aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRuss Allbery <eagle@eyrie.org>2014-12-08 20:57:57 -0800
committerRuss Allbery <eagle@eyrie.org>2014-12-08 20:57:57 -0800
commit7856dc7cc5e16140c0084474fe54338f293bf77e (patch)
tree5948678fb9c0a30b7d72057c9952ac8836ae2499
parentdd295a55a6f02e7585a9f5be9e8b434c6d14d040 (diff)
parente73a80c6bc23f16544c35e7dc3bf61ca9292c3b5 (diff)
Imported Upstream version 1.2upstream/1.2
-rw-r--r--Makefile.am43
-rw-r--r--Makefile.in51
-rw-r--r--NEWS19
-rw-r--r--README2
-rw-r--r--TODO221
-rw-r--r--aclocal.m49
-rw-r--r--client/wallet-rekey.14
-rw-r--r--client/wallet.14
-rwxr-xr-xconfigure25
-rw-r--r--configure.ac3
-rw-r--r--contrib/wallet-rekey-periodic.84
-rw-r--r--contrib/wallet-summary.84
-rw-r--r--contrib/wallet-unknown-hosts.84
-rw-r--r--perl/lib/Wallet/Admin.pm4
-rw-r--r--perl/lib/Wallet/Config.pm6
-rw-r--r--perl/lib/Wallet/Object/Base.pm2
-rw-r--r--perl/lib/Wallet/Object/Duo.pm54
-rw-r--r--perl/lib/Wallet/Object/Duo/LDAPProxy.pm202
-rw-r--r--perl/lib/Wallet/Object/Duo/PAM.pm205
-rw-r--r--perl/lib/Wallet/Object/Duo/RDP.pm204
-rw-r--r--perl/lib/Wallet/Object/Duo/RadiusProxy.pm204
-rw-r--r--perl/lib/Wallet/Object/File.pm56
-rw-r--r--perl/lib/Wallet/Policy/Stanford.pm24
-rw-r--r--perl/lib/Wallet/Schema.pm2
-rw-r--r--perl/lib/Wallet/Schema/Result/Duo.pm14
-rw-r--r--perl/lib/Wallet/Server.pm46
-rw-r--r--perl/sql/Wallet-Schema-0.09-0.10-MySQL.sql14
-rw-r--r--perl/sql/Wallet-Schema-0.09-0.10-PostgreSQL.sql18
-rw-r--r--perl/sql/Wallet-Schema-0.09-0.10-SQLite.sql26
-rw-r--r--perl/sql/Wallet-Schema-0.10-MySQL.sql209
-rw-r--r--perl/sql/Wallet-Schema-0.10-PostgreSQL.sql216
-rw-r--r--perl/sql/Wallet-Schema-0.10-SQLite.sql220
-rw-r--r--perl/t/data/duo/integration-ldap.json11
-rw-r--r--perl/t/data/duo/integration-radius.json11
-rw-r--r--perl/t/data/duo/integration-rdp.json11
-rwxr-xr-xperl/t/general/admin.t2
-rw-r--r--perl/t/object/duo-ldap.t160
-rw-r--r--perl/t/object/duo-pam.t159
-rw-r--r--perl/t/object/duo-radius.t165
-rw-r--r--perl/t/object/duo-rdp.t158
-rwxr-xr-xperl/t/object/duo.t7
-rwxr-xr-xperl/t/object/file.t10
-rw-r--r--server/keytab-backend.84
-rw-r--r--server/wallet-admin.84
-rwxr-xr-xserver/wallet-backend9
-rw-r--r--server/wallet-backend.89
-rw-r--r--server/wallet-report.84
47 files changed, 2619 insertions, 224 deletions
diff --git a/Makefile.am b/Makefile.am
index d6409ea..ccbaed0 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -26,8 +26,11 @@ PERL_FILES = perl/Build.PL perl/MANIFEST perl/MANIFEST.SKIP perl/create-ddl \
perl/lib/Wallet/Config.pm perl/lib/Wallet/Database.pm \
perl/lib/Wallet/Kadmin.pm perl/lib/Wallet/Kadmin/Heimdal.pm \
perl/lib/Wallet/Kadmin/MIT.pm perl/lib/Wallet/Object/Base.pm \
- perl/lib/Wallet/Object/Duo.pm perl/lib/Wallet/Object/File.pm \
- perl/lib/Wallet/Object/Keytab.pm \
+ perl/lib/Wallet/Object/Duo.pm \
+ perl/lib/Wallet/Object/Duo/LDAPProxy.pm \
+ perl/lib/Wallet/Object/Duo/PAM.pm perl/lib/Wallet/Object/Duo/RDP.pm \
+ perl/lib/Wallet/Object/Duo/RadiusProxy.pm \
+ perl/lib/Wallet/Object/File.pm perl/lib/Wallet/Object/Keytab.pm \
perl/lib/Wallet/Object/WAKeyring.pm \
perl/lib/Wallet/Policy/Stanford.pm perl/lib/Wallet/Report.pm \
perl/lib/Wallet/Schema.pm perl/lib/Wallet/Server.pm \
@@ -56,15 +59,26 @@ PERL_FILES = perl/Build.PL perl/MANIFEST perl/MANIFEST.SKIP perl/create-ddl \
perl/sql/Wallet-Schema-0.08-SQLite.sql \
perl/sql/Wallet-Schema-0.09-MySQL.sql \
perl/sql/Wallet-Schema-0.09-PostgreSQL.sql \
- perl/sql/Wallet-Schema-0.09-SQLite.sql perl/t/data/README \
- perl/t/data/duo/integration.json perl/t/data/duo/keys.json \
+ perl/sql/Wallet-Schema-0.09-SQLite.sql \
+ perl/sql/Wallet-Schema-0.09-0.10-MySQL.sql \
+ perl/sql/Wallet-Schema-0.09-0.10-PostgreSQL.sql \
+ perl/sql/Wallet-Schema-0.09-0.10-SQLite.sql \
+ perl/sql/Wallet-Schema-0.10-MySQL.sql \
+ perl/sql/Wallet-Schema-0.10-PostgreSQL.sql \
+ perl/sql/Wallet-Schema-0.10-SQLite.sql perl/t/data/README \
+ perl/t/data/duo/integration.json \
+ perl/t/data/duo/integration-ldap.json \
+ perl/t/data/duo/integration-radius.json \
+ perl/t/data/duo/integration-rdp.json perl/t/data/duo/keys.json \
perl/t/data/keytab-fake perl/t/data/keytab.conf \
perl/t/data/netdb-fake perl/t/data/netdb.conf perl/t/data/perl.conf \
perl/t/docs/pod-spelling.t perl/t/docs/pod.t perl/t/general/acl.t \
perl/t/general/admin.t perl/t/general/config.t \
perl/t/general/init.t perl/t/general/report.t \
perl/t/general/server.t perl/t/lib/Util.pm perl/t/object/base.t \
- perl/t/object/duo.t perl/t/object/file.t perl/t/object/keytab.t \
+ perl/t/object/duo.t perl/t/object/duo-ldap.t \
+ perl/t/object/duo-pam.t perl/t/object/duo-radius.t \
+ perl/t/object/duo-rdp.t perl/t/object/file.t perl/t/object/keytab.t \
perl/t/object/wa-keyring.t perl/t/policy/stanford.t \
perl/t/style/minimum-version.t perl/t/style/strict.t \
perl/t/util/kadmin.t perl/t/verifier/basic.t \
@@ -72,14 +86,14 @@ PERL_FILES = perl/Build.PL perl/MANIFEST perl/MANIFEST.SKIP perl/create-ddl \
# Directories that have to be created in builddir != srcdir builds before
# copying PERL_FILES over.
-PERL_DIRECTORIES = perl perl/lib perl/lib/Wallet perl/lib/Wallet/ACL \
- perl/lib/Wallet/ACL/Krb5 perl/lib/Wallet/ACL/LDAP \
- perl/lib/Wallet/ACL/NetDB perl/lib/Wallet/Kadmin \
- perl/lib/Wallet/Object perl/lib/Wallet/Policy \
- perl/lib/Wallet/Schema perl/lib/Wallet/Schema/Result perl/sql \
- perl/t perl/t/data perl/t/data/duo perl/t/docs perl/t/general \
- perl/t/lib perl/t/object perl/t/policy perl/t/style perl/t/util \
- perl/t/verifier
+PERL_DIRECTORIES = perl perl/lib perl/lib/Wallet perl/lib/Wallet/ACL \
+ perl/lib/Wallet/ACL/Krb5 perl/lib/Wallet/ACL/LDAP \
+ perl/lib/Wallet/ACL/NetDB perl/lib/Wallet/Kadmin \
+ perl/lib/Wallet/Object perl/lib/Wallet/Object/Duo \
+ perl/lib/Wallet/Policy perl/lib/Wallet/Schema \
+ perl/lib/Wallet/Schema/Result perl/sql perl/t perl/t/data \
+ perl/t/data/duo perl/t/docs perl/t/general perl/t/lib perl/t/object \
+ perl/t/policy perl/t/style perl/t/util perl/t/verifier
ACLOCAL_AMFLAGS = -I m4
EXTRA_DIST = .gitignore LICENSE autogen client/wallet.pod \
@@ -205,9 +219,8 @@ perl/blib/lib/Wallet/Config.pm: $(srcdir)/perl/lib/Wallet/Config.pm
cp "$(srcdir)/$$f" "$(builddir)/$$f" ; \
done ; \
fi
- mkdir perl/t/lib/Test
+ $(MKDIR_P) perl/t/lib/Test/RRA
$(INSTALL_DATA) $(srcdir)/tests/tap/perl/Test/RRA.pm perl/t/lib/Test/
- mkdir perl/t/lib/Test/RRA
$(INSTALL_DATA) $(srcdir)/tests/tap/perl/Test/RRA/Config.pm \
perl/t/lib/Test/RRA/
cd perl && perl Build.PL $(WALLET_PERL_FLAGS)
diff --git a/Makefile.in b/Makefile.in
index 8b55c96..0fc38ed 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -105,12 +105,12 @@ DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/Makefile.am \
$(top_srcdir)/tests/client/prompt-t.in \
$(top_srcdir)/tests/client/rekey-t.in \
$(top_srcdir)/portable/setenv.c \
- $(top_srcdir)/portable/mkstemp.c \
- $(top_srcdir)/portable/reallocarray.c \
+ $(top_srcdir)/portable/strlcpy.c \
$(top_srcdir)/portable/snprintf.c \
- $(top_srcdir)/portable/asprintf.c \
+ $(top_srcdir)/portable/reallocarray.c \
$(top_srcdir)/portable/strlcat.c \
- $(top_srcdir)/portable/strlcpy.c $(dist_sbin_SCRIPTS) \
+ $(top_srcdir)/portable/asprintf.c \
+ $(top_srcdir)/portable/mkstemp.c $(dist_sbin_SCRIPTS) \
$(top_srcdir)/build-aux/depcomp $(dist_man_MANS) \
$(dist_pkgdata_DATA) NEWS README TODO build-aux/ar-lib \
build-aux/compile build-aux/depcomp build-aux/install-sh \
@@ -502,8 +502,11 @@ PERL_FILES = perl/Build.PL perl/MANIFEST perl/MANIFEST.SKIP perl/create-ddl \
perl/lib/Wallet/Config.pm perl/lib/Wallet/Database.pm \
perl/lib/Wallet/Kadmin.pm perl/lib/Wallet/Kadmin/Heimdal.pm \
perl/lib/Wallet/Kadmin/MIT.pm perl/lib/Wallet/Object/Base.pm \
- perl/lib/Wallet/Object/Duo.pm perl/lib/Wallet/Object/File.pm \
- perl/lib/Wallet/Object/Keytab.pm \
+ perl/lib/Wallet/Object/Duo.pm \
+ perl/lib/Wallet/Object/Duo/LDAPProxy.pm \
+ perl/lib/Wallet/Object/Duo/PAM.pm perl/lib/Wallet/Object/Duo/RDP.pm \
+ perl/lib/Wallet/Object/Duo/RadiusProxy.pm \
+ perl/lib/Wallet/Object/File.pm perl/lib/Wallet/Object/Keytab.pm \
perl/lib/Wallet/Object/WAKeyring.pm \
perl/lib/Wallet/Policy/Stanford.pm perl/lib/Wallet/Report.pm \
perl/lib/Wallet/Schema.pm perl/lib/Wallet/Server.pm \
@@ -532,15 +535,26 @@ PERL_FILES = perl/Build.PL perl/MANIFEST perl/MANIFEST.SKIP perl/create-ddl \
perl/sql/Wallet-Schema-0.08-SQLite.sql \
perl/sql/Wallet-Schema-0.09-MySQL.sql \
perl/sql/Wallet-Schema-0.09-PostgreSQL.sql \
- perl/sql/Wallet-Schema-0.09-SQLite.sql perl/t/data/README \
- perl/t/data/duo/integration.json perl/t/data/duo/keys.json \
+ perl/sql/Wallet-Schema-0.09-SQLite.sql \
+ perl/sql/Wallet-Schema-0.09-0.10-MySQL.sql \
+ perl/sql/Wallet-Schema-0.09-0.10-PostgreSQL.sql \
+ perl/sql/Wallet-Schema-0.09-0.10-SQLite.sql \
+ perl/sql/Wallet-Schema-0.10-MySQL.sql \
+ perl/sql/Wallet-Schema-0.10-PostgreSQL.sql \
+ perl/sql/Wallet-Schema-0.10-SQLite.sql perl/t/data/README \
+ perl/t/data/duo/integration.json \
+ perl/t/data/duo/integration-ldap.json \
+ perl/t/data/duo/integration-radius.json \
+ perl/t/data/duo/integration-rdp.json perl/t/data/duo/keys.json \
perl/t/data/keytab-fake perl/t/data/keytab.conf \
perl/t/data/netdb-fake perl/t/data/netdb.conf perl/t/data/perl.conf \
perl/t/docs/pod-spelling.t perl/t/docs/pod.t perl/t/general/acl.t \
perl/t/general/admin.t perl/t/general/config.t \
perl/t/general/init.t perl/t/general/report.t \
perl/t/general/server.t perl/t/lib/Util.pm perl/t/object/base.t \
- perl/t/object/duo.t perl/t/object/file.t perl/t/object/keytab.t \
+ perl/t/object/duo.t perl/t/object/duo-ldap.t \
+ perl/t/object/duo-pam.t perl/t/object/duo-radius.t \
+ perl/t/object/duo-rdp.t perl/t/object/file.t perl/t/object/keytab.t \
perl/t/object/wa-keyring.t perl/t/policy/stanford.t \
perl/t/style/minimum-version.t perl/t/style/strict.t \
perl/t/util/kadmin.t perl/t/verifier/basic.t \
@@ -549,14 +563,14 @@ PERL_FILES = perl/Build.PL perl/MANIFEST perl/MANIFEST.SKIP perl/create-ddl \
# Directories that have to be created in builddir != srcdir builds before
# copying PERL_FILES over.
-PERL_DIRECTORIES = perl perl/lib perl/lib/Wallet perl/lib/Wallet/ACL \
- perl/lib/Wallet/ACL/Krb5 perl/lib/Wallet/ACL/LDAP \
- perl/lib/Wallet/ACL/NetDB perl/lib/Wallet/Kadmin \
- perl/lib/Wallet/Object perl/lib/Wallet/Policy \
- perl/lib/Wallet/Schema perl/lib/Wallet/Schema/Result perl/sql \
- perl/t perl/t/data perl/t/data/duo perl/t/docs perl/t/general \
- perl/t/lib perl/t/object perl/t/policy perl/t/style perl/t/util \
- perl/t/verifier
+PERL_DIRECTORIES = perl perl/lib perl/lib/Wallet perl/lib/Wallet/ACL \
+ perl/lib/Wallet/ACL/Krb5 perl/lib/Wallet/ACL/LDAP \
+ perl/lib/Wallet/ACL/NetDB perl/lib/Wallet/Kadmin \
+ perl/lib/Wallet/Object perl/lib/Wallet/Object/Duo \
+ perl/lib/Wallet/Policy perl/lib/Wallet/Schema \
+ perl/lib/Wallet/Schema/Result perl/sql perl/t perl/t/data \
+ perl/t/data/duo perl/t/docs perl/t/general perl/t/lib perl/t/object \
+ perl/t/policy perl/t/style perl/t/util perl/t/verifier
ACLOCAL_AMFLAGS = -I m4
EXTRA_DIST = .gitignore LICENSE autogen client/wallet.pod \
@@ -1899,9 +1913,8 @@ perl/blib/lib/Wallet/Config.pm: $(srcdir)/perl/lib/Wallet/Config.pm
cp "$(srcdir)/$$f" "$(builddir)/$$f" ; \
done ; \
fi
- mkdir perl/t/lib/Test
+ $(MKDIR_P) perl/t/lib/Test/RRA
$(INSTALL_DATA) $(srcdir)/tests/tap/perl/Test/RRA.pm perl/t/lib/Test/
- mkdir perl/t/lib/Test/RRA
$(INSTALL_DATA) $(srcdir)/tests/tap/perl/Test/RRA/Config.pm \
perl/t/lib/Test/RRA/
cd perl && perl Build.PL $(WALLET_PERL_FLAGS)
diff --git a/NEWS b/NEWS
index 039494e..272b109 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,24 @@
User-Visible wallet Changes
+wallet 1.2 (2014-12-08)
+
+ The duo object type has been split into several sub-types, each for a
+ specific type of Duo integration. The old type's functionality has
+ been moved to duo-pam (Wallet::Object::Duo::PAM), and new types are
+ supported for Duo's auth proxy configurations for LDAP and Radius, and
+ their RDP configuration. These types are duo-radius, duo-ldap, and
+ duo-rdp (Wallet::Object::Duo::RadiusProxy,
+ Wallet::Object::Duo::LDAPProxy, and Wallet::Object::Duo::RDP). The
+ old duo type still exists for compatability. To enable these object
+ types for an existing wallet database, use wallet-admin to register the
+ new object.
+
+ New rename command for file type objects. This will change the name
+ of the object itself and move any stored data for the file to the
+ correct location for the new name. Currently, rename is only
+ supported for file objects, but may be supported by other backends in
+ the future.
+
wallet 1.1 (2014-07-16)
A new object type, duo (Wallet::Object::Duo), is now supported. This
diff --git a/README b/README
index 0a6b4a6..f21b9ad 100644
--- a/README
+++ b/README
@@ -1,4 +1,4 @@
- wallet release 1.1
+ wallet release 1.2
(secure data management system)
Written by Russ Allbery <eagle@eyrie.org>
diff --git a/TODO b/TODO
index a8d0aef..4babbca 100644
--- a/TODO
+++ b/TODO
@@ -2,163 +2,162 @@
Client:
- * WALLET-5: Handle duplicate kvnos in a newly returned keytab and an
+ * KERB-94: Handle duplicate kvnos in a newly returned keytab and an
existing keytab (such as when downloading an unchanging keytab and
merging it into an existing one) in some reasonable fashion.
- * WALLET-6: Support removing old kvnos from a merged keytab (similar to
+ * KERB-90: Support removing old kvnos from a merged keytab (similar to
kadmin ktremove old).
- * WALLET-7: When reading configuration from krb5.conf, we should first
- try to determine our principal from any existing Kerberos ticket cache
+ * KERB-88: When reading configuration from krb5.conf, we should first try
+ to determine our principal from any existing Kerberos ticket cache
(after obtaining tickets if -u was given) and extract the realm from
that principal, using it as the default realm when reading
configuration information.
- * WALLET-8: Add readline support to the wallet client to make it easier
- to issue multiple commands.
+ * KERB-89: Add readline support to the wallet client to make it easier to
+ issue multiple commands.
- * WALLET-9: Support authenticating with a keytab.
+ * KERB-115: Support authenticating with a keytab.
- * WALLET-10: When obtaining tickets in the wallet client with -u,
- directly obtain the service ticket we're going to use for remctl.
+ * KERB-97: When obtaining tickets in the wallet client with -u, directly
+ obtain the service ticket we're going to use for remctl.
- * WALLET-11: Provide a way to refresh a file object if and only if what's
+ * KERB-95: Provide a way to refresh a file object if and only if what's
stored on the server is different than what's on disk. This will
require server support as well for returning the checksum of a file.
- * WALLET-80: Incorporate the wallet-rekey-periodic script (currently in
+ * KERB-104: Incorporate the wallet-rekey-periodic script (currently in
contrib) into the package and teach it how to ignore foreign
credentials.
Server Interface:
- * WALLET-13: Provide a way to get history for deleted objects and ACLs.
+ * KERB-126: Provide a way to get history for deleted objects and ACLs.
- * WALLET-14: Provide an interface to mass-change all instances of one ACL
+ * KERB-66: Provide an interface to mass-change all instances of one ACL
to another.
- * WALLET-15: Add help functions to wallet-backend, wallet-report, and
+ * KERB-96: Add help functions to wallet-backend, wallet-report, and
wallet-admin listing the commands.
- * WALLET-16: Catch exceptions on object creation in wallet-backend so
- that we can log those as well.
+ * KERB-52: Catch exceptions on object creation in wallet-backend so that
+ we can log those as well.
- * WALLET-17: Provide a way to list all objects for which the connecting
+ * KERB-114: Provide a way to list all objects for which the connecting
user has ACLs.
- * WALLET-18: Support limiting returned history information by timestamp.
+ * KERB-101: Support limiting returned history information by timestamp.
- * WALLET-19: Provide a REST implementation of the wallet server.
+ * KERB-128: Provide a REST implementation of the wallet server.
- * WALLET-20: Provide a CGI implementation of the wallet server.
+ * KERB-79: Provide a CGI implementation of the wallet server.
- * WALLET-21: Support setting flags and attributes on autocreate. In
+ * KERB-111: Support setting flags and attributes on autocreate. In
general, work out a Wallet::Object::Template Perl object that I can
return that specifies things other than just the ACL.
- * WALLET-22: Remove the hard-coded ADMIN ACL in the server with something
+ * KERB-93: Remove the hard-coded ADMIN ACL in the server with something
more configurable, perhaps a global ACL table or something.
- * WALLET-63: Support leap-of-faith keying of systems by registering an
+ * KERB-68: Support leap-of-faith keying of systems by registering an
object for one-time download (ideally from a specific IP address) and
then allowing that object to be downloaded anonymously from that IP.
Relies on support for Kerberos anonymous authentication.
- * WALLET-64: Split "get" and "update" in semantics, and only do keytab
+ * KERB-84: Split "get" and "update" in semantics, and only do keytab
rekeying on update. "get" would not be permitted unless the keytab was
flagged as unchanging, and update would still change even an unchanging
keytab (maybe). Or, alternately, maybe we allow get of any keytab?
Requires more thought.
- * WALLET-69: "owner" should print the name as well as the number of the
- ACL. Also check "getacl".
+ * KERB-118: Add command to list available types and schemes.
- * WALLET-70: Add command to list available types and schemes.
-
- * WALLET-72: Add a mechanism to automate owner updates based on
+ * KERB-75: Add a mechanism to automate owner updates based on
default_owner.
- * WALLET-79: Partially merge create and autocreate. create and autocreate
- should do the same thing provided there is an autocreation configuration
- available. If not, autocreate should fail and create should fall back on
- checking for ADMIN privileges.
+ * KERB-64: Partially merge create and autocreate. create and autocreate
+ should do the same thing provided there is an autocreation
+ configuration available. If not, autocreate should fail and create
+ should fall back on checking for ADMIN privileges.
- * WALLET-83: Support file object renaming.
+ * KERB-116: Support file object renaming.
- * Rewrite server backends to use Net::Remctl::Backend.
+ * KERB-131: Rewrite server backends to use Net::Remctl::Backend.
- * Merge the Wallet::Logger support written by Commerzbank AG: create a
- new class that handles logging, probably based on Log::Log4perl, and
- add logging points to all of the core classes.
+ * KERB-132: Merge the Wallet::Logger support written by Commerzbank AG:
+ create a new class that handles logging, probably based on
+ Log::Log4perl, and add logging points to all of the core classes.
- * Support an authorization hook to determine whether or not to permit
- autocreate. One requested example feature is to limit autocreate of
- keytab objects to certain hosts involved in deployment. It should be
- possible to write a hook that takes the information about what object
- is being autocreated and can accept or decline.
+ * KERB-133: Support an authorization hook to determine whether or not to
+ permit autocreate. One requested example feature is to limit
+ autocreate of keytab objects to certain hosts involved in deployment.
+ It should be possible to write a hook that takes the information about
+ what object is being autocreated and can accept or decline.
ACLs:
- * WALLET-23: Error messages from ACL operations should refer to the ACLs
+ * KERB-119: Error messages from ACL operations should refer to the ACLs
by name instead of by ID.
- * WALLET-24: Write the PTS ACL verifier.
+ * KERB-121: Write the PTS ACL verifier.
- * WALLET-25: Rename Wallet::ACL::* to Wallet::Verifier::*. Add
+ * KERB-123: Rename Wallet::ACL::* to Wallet::Verifier::*. Add
Wallet::ACL as a generic interface with Wallet::ACL::Database and
Wallet::ACL::List implementations (or some similar name) so that we can
create and check an ACL without having to write it into the database.
Redo default ACL creation using that functionality.
- * WALLET-26: Pass a reference to the object for which the ACL is
+ * KERB-67: Pass a reference to the object for which the ACL is
interpreted to the ACL API so that ACL APIs can make more complex
decisions.
- * WALLET-27: A group-in-groups ACL schema.
+ * KERB-109: A group-in-groups ACL schema.
- * WALLET-28: Provide an API for verifiers to syntax-check the values
+ * KERB-113: Provide an API for verifiers to syntax-check the values
before an ACL is set and implement syntax checking for the krb5 and
ldap-attr verifiers.
- * WALLET-29: Investigate how best to support client authentication using
+ * KERB-60: Investigate how best to support client authentication using
anonymous PKINIT for things like initial system keying.
- * WALLET-68: Generalize the current NetDB ACL type to allow a generic
+ * KERB-72: Generalize the current NetDB ACL type to allow a generic
remctl query for whether a particular user is authorized to create
host-based objects for a particular host.
- * WALLET-71: Add ldap-group ACL scheme.
+ * KERB-78: Add ldap-group ACL scheme.
- * WALLET-75: Provide a root-instance version of the ldap-attr (and
- possibly the ldap-group) ACL schemes.
+ * KERB-63: Provide a root-instance version of the ldap-attr (and possibly
+ the ldap-group) ACL schemes.
- * WALLET-81: Add a comment field to ACLs.
+ * KERB-86: Add a comment field to ACLs.
Database:
- * WALLET-30: Fix case-insensitivity bug in unique keys with MySQL for
- objects.
+ * KERB-55: Fix case-insensitivity bug in unique keys with MySQL for
+ objects. When creating an http/<host> principal when an HTTP/<host>
+ principal already existed, MySQL rejected the row entry as a duplicate.
+ The name should be case-sensitive.
- * WALLET-31: On upgrades, support adding new object types and ACL
+ * KERB-103: On upgrades, support adding new object types and ACL
verifiers to the class tables.
Objects:
- * WALLET-32: Check whether we can just drop the realm restriction on
+ * KERB-120: Check whether we can just drop the realm restriction on
keytabs and allow the name to contain the realm if the Kerberos type is
Heimdal.
- * WALLET-33: Use the Perl Authen::Krb5::Admin module instead of rolling
- our own kadmin code with Expect now that MIT Kerberos has made the
- kadmin API public.
+ * KERB-59: Use the Perl Authen::Krb5::Admin module instead of rolling our
+ own kadmin code with Expect now that MIT Kerberos has made the kadmin
+ API public.
- * WALLET-34: Implement an ssh keypair wallet object. The server can run
+ * KERB-85: Implement an ssh keypair wallet object. The server can run
ssh-keygen to generate a public/private key pair and return both to the
client, which would split them apart. Used primarily for host keys.
May need a side table to store key types, or a naming convention.
- * WALLET-35: Implement an X.509 certificate object. I expect this would
+ * KERB-124: Implement an X.509 certificate object. I expect this would
store the public and private key as a single file in the same format
that Apache can read for combined public and private keys. There were
requests for storing the CSR, but I don't see why you'd want to do
@@ -166,127 +165,127 @@ Objects:
here, but it would be nice to automatically support object expiration
based on the expiration time for the certificate.
- * WALLET-36: Implement an X.509 CA so that you can get certificate
- objects without storing them first. Need to resolve naming conventions
- if you want to run multiple CAs on the same wallet server (but why?).
- Should this be a different type than stored certificates? Consider
- using hxtool as the underlying CA mechanism.
+ * KERB-106: Implement an X.509 CA so that you can get certificate objects
+ without storing them first. Need to resolve naming conventions if you
+ want to run multiple CAs on the same wallet server (but why?). Should
+ this be a different type than stored certificates? Consider using
+ hxtool as the underlying CA mechanism.
- * WALLET-37: Support returning the checksum of a file object stored in
+ * KERB-77: Support returning the checksum of a file object stored in
wallet so that one can determine whether the version stored on disk is
identical.
- * WALLET-60: Implement new password wallet object, which is like file
+ * KERB-108: Implement new password wallet object, which is like file
except that it generates a random, strong password when retrieved the
first time without being stored.
- * WALLET-61: Support interrogating objects to find all host-based objects
+ * KERB-71: Support interrogating objects to find all host-based objects
for a particular host, allowing cleanup of all of those host's objects
after retiring the host.
- * WALLET-76: Support setting the disallow-svr flag on created principals.
+ * KERB-127: Support setting the disallow-svr flag on created principals.
In general, support setting arbitrary principal flags.
Reports:
- * WALLET-38: Add audit for references to unknown ACLs, possibly
- introduced by previous versions before ACL deletion was checked with
- database backends that don't do referential integrity.
+ * KERB-117: Add audit for references to unknown ACLs, possibly introduced
+ by previous versions before ACL deletion was checked with database
+ backends that don't do referential integrity.
- * WALLET-39: Add report for all objects that have never been stored.
+ * KERB-105: Add report for all objects that have never been stored.
- * WALLET-40: For objects tied to hostnames, report on objects referring
- to hosts which do not exist. For the initial pass, this is probably
- only keytab objects with names containing a slash where the part after
- the slash looks like a hostname. This may need some configuration
- help.
+ * KERB-122: For objects tied to hostnames, report on objects referring to
+ hosts which do not exist. For the initial pass, this is probably only
+ keytab objects with names containing a slash where the part after the
+ slash looks like a hostname. This may need some configuration help.
- * WALLET-41: Make contrib/wallet-summary generic and include it in
+ * KERB-102: Make contrib/wallet-summary generic and include it in
wallet-report, with additional configuration in Wallet::Config.
Enhance it to report on any sort of object, not just on keytabs, and to
give numbers on downloaded versus not downloaded objects.
- * WALLET-62: Write a tool to mail the owners of wallet objects, taking
- the list of objects and the mail message to send as inputs. This could
+ * KERB-69: Write a tool to mail the owners of wallet objects, taking the
+ list of objects and the mail message to send as inputs. This could
possibly use the notification service, although a version that sends
mail directly would be useful external to Stanford.
- * Merge the Commerzbank AG work to dump all the object history, applying
- various search criteria to it, or clear parts of the object history.
+ * KERB-134: Merge the Commerzbank AG work to dump all the object history,
+ applying various search criteria to it, or clear parts of the object
+ history.
Administrative Interface:
- * WALLET-42: Add a function to wallet-admin to purge expired entries.
+ * KERB-80: Add a function to wallet-admin to purge expired entries.
Possibly also check expiration before allowing anyone to get or store
objects.
- * WALLET-3: Add a function or separate script to automate removal of
+ * KERB-58: Add a function or separate script to automate removal of
DNS-based objects for which the hosts no longer exist. Will need to
support a site-specific callout to determine whether the host exists.
- * WALLET-66: Database creation appears not to work without the SQL files,
+ * KERB-54: Database creation appears not to work without the SQL files,
but it's supposed to work directly from the classes. Double-check
this.
Documentation:
- * WALLET-43: Write a conventions document for ACL naming, object naming,
+ * KERB-82: Write a conventions document for ACL naming, object naming,
and similar issues.
- * WALLET-44: Write a future design and roadmap document to collect notes
+ * KERB-125: Write a future design and roadmap document to collect notes
about how unimplemented features should be handled.
- * WALLET-45: Document using the wallet system over something other than
+ * KERB-65: Document using the wallet system over something other than
remctl.
- * WALLET-46: Document all diagnostics for all wallet APIs.
+ * KERB-112: Document all diagnostics for all wallet APIs.
- * Document configuration with an Oracle database.
+ * KERB-135: Document configuration with an Oracle database.
Code Style and Cleanup:
- * WALLET-47: There is a lot of duplicate code in wallet-backend. Convert
+ * KERB-98: There is a lot of duplicate code in wallet-backend. Convert
that to use some sort of data-driven model with argument count and
flags so that the method calls can be written only once. Convert
wallet-admin to use the same code.
- * WALLET-48: There's a lot of code duplication in the dispatch functions
+ * KERB-100: There's a lot of code duplication in the dispatch functions
in the Wallet::Server class. Find a way to rewrite that so that the
dispatch doesn't duplicate the same code patterns.
- * WALLET-49: The wallet-backend and wallet documentation share the
- COMMANDS section. Work out some means to assemble the documentation
- without duplicating content.
+ * KERB-73: The wallet-backend and wallet documentation share the COMMANDS
+ section. Work out some means to assemble the documentation without
+ duplicating content.
- * WALLET-50: The Wallet::Config class is very ugly and could use some
+ * KERB-110: The Wallet::Config class is very ugly and could use some
better internal API to reference the variables in it.
- * WALLET-52: Consider using Class::Accessor to get rid of the scaffolding
+ * KERB-76: Consider using Class::Accessor to get rid of the scaffolding
code to access object data. Alternately, consider using Moose.
- * Rewrite the error handling to use exceptions instead of the C-style
- return value and separate error call.
+ * KERB-130: Rewrite the error handling to use exceptions instead of the
+ C-style return value and separate error call.
Test Suite:
- * WALLET-53: The ldap-attr verifier test case is awful and completely
+ * KERB-92: The ldap-attr verifier test case is awful and completely
specific to people with admin access to the Stanford LDAP tree. Write
a real test.
- * WALLET-54: Rename the tests to use a subdirectory organization.
+ * KERB-87: Rename the tests to use a subdirectory organization.
- * WALLET-55: Add POD coverage testing using Test::POD::Coverage for the
+ * KERB-61: Add POD coverage testing using Test::POD::Coverage for the
server modules.
- * WALLET-56: Rewrite the client test suite to use Perl and to make better
+ * KERB-91: Rewrite the client test suite to use Perl and to make better
use of shared code so that it can be broken into function components.
- * WALLET-57: Refactor the test suite for the wallet backend to try to
+ * KERB-74: Refactor the test suite for the wallet backend to try to
reduce the duplicated code. Using a real mock infrastructure should
make this test suite much easier to write.
- * WALLET-58: Pull common test suite code into a Perl library that can be
+ * KERB-81: Pull common test suite code into a Perl library that can be
reused.
- * WALLET-59: Write a test suite to scan all wallet code looking for
+ * KERB-99: Write a test suite to scan all wallet code looking for
diagnostics that aren't in the documentation and warn about them.
diff --git a/aclocal.m4 b/aclocal.m4
index 3b07628..6145011 100644
--- a/aclocal.m4
+++ b/aclocal.m4
@@ -21,7 +21,7 @@ If you have problems, you may need to regenerate the build system entirely.
To do so, use the procedure documented by the package, typically 'autoreconf'.])])
# longlong.m4 serial 17
-dnl Copyright (C) 1999-2007, 2009-2013 Free Software Foundation, Inc.
+dnl Copyright (C) 1999-2007, 2009-2014 Free Software Foundation, Inc.
dnl This file is free software; the Free Software Foundation
dnl gives unlimited permission to copy and/or distribute it,
dnl with or without modifications, as long as this notice is preserved.
@@ -277,10 +277,9 @@ AC_SUBST([AR])dnl
# configured tree to be moved without reconfiguration.
AC_DEFUN([AM_AUX_DIR_EXPAND],
-[dnl Rely on autoconf to set up CDPATH properly.
-AC_PREREQ([2.50])dnl
-# expand $ac_aux_dir to an absolute path
-am_aux_dir=`cd $ac_aux_dir && pwd`
+[AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT])dnl
+# Expand $ac_aux_dir to an absolute path.
+am_aux_dir=`cd "$ac_aux_dir" && pwd`
])
# AM_CONDITIONAL -*- Autoconf -*-
diff --git a/client/wallet-rekey.1 b/client/wallet-rekey.1
index cd26421..77b2f11 100644
--- a/client/wallet-rekey.1
+++ b/client/wallet-rekey.1
@@ -1,4 +1,4 @@
-.\" Automatically generated by Pod::Man 2.27 (Pod::Simple 3.28)
+.\" Automatically generated by Pod::Man 2.28 (Pod::Simple 3.28)
.\"
.\" Standard preamble:
.\" ========================================================================
@@ -133,7 +133,7 @@
.\" ========================================================================
.\"
.IX Title "WALLET-REKEY 1"
-.TH WALLET-REKEY 1 "2014-07-16" "1.1" "wallet"
+.TH WALLET-REKEY 1 "2014-12-08" "1.2" "wallet"
.\" For nroff, turn off justification. Always turn off hyphenation; it makes
.\" way too many mistakes in technical documents.
.if n .ad l
diff --git a/client/wallet.1 b/client/wallet.1
index 4376f32..1f41073 100644
--- a/client/wallet.1
+++ b/client/wallet.1
@@ -1,4 +1,4 @@
-.\" Automatically generated by Pod::Man 2.27 (Pod::Simple 3.28)
+.\" Automatically generated by Pod::Man 2.28 (Pod::Simple 3.28)
.\"
.\" Standard preamble:
.\" ========================================================================
@@ -133,7 +133,7 @@
.\" ========================================================================
.\"
.IX Title "WALLET 1"
-.TH WALLET 1 "2014-07-16" "1.1" "wallet"
+.TH WALLET 1 "2014-12-08" "1.2" "wallet"
.\" For nroff, turn off justification. Always turn off hyphenation; it makes
.\" way too many mistakes in technical documents.
.if n .ad l
diff --git a/configure b/configure
index ad21178..bdbdf43 100755
--- a/configure
+++ b/configure
@@ -1,6 +1,6 @@
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.69 for wallet 1.1.
+# Generated by GNU Autoconf 2.69 for wallet 1.2.
#
# Report bugs to <eagle@eyrie.org>.
#
@@ -580,8 +580,8 @@ MAKEFLAGS=
# Identity of this package.
PACKAGE_NAME='wallet'
PACKAGE_TARNAME='wallet'
-PACKAGE_VERSION='1.1'
-PACKAGE_STRING='wallet 1.1'
+PACKAGE_VERSION='1.2'
+PACKAGE_STRING='wallet 1.2'
PACKAGE_BUGREPORT='eagle@eyrie.org'
PACKAGE_URL=''
@@ -1302,7 +1302,7 @@ if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
-\`configure' configures wallet 1.1 to adapt to many kinds of systems.
+\`configure' configures wallet 1.2 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
@@ -1368,7 +1368,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
- short | recursive ) echo "Configuration of wallet 1.1:";;
+ short | recursive ) echo "Configuration of wallet 1.2:";;
esac
cat <<\_ACEOF
@@ -1486,7 +1486,7 @@ fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
-wallet configure 1.1
+wallet configure 1.2
generated by GNU Autoconf 2.69
Copyright (C) 2012 Free Software Foundation, Inc.
@@ -2195,7 +2195,7 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
-It was created by wallet $as_me 1.1, which was
+It was created by wallet $as_me 1.2, which was
generated by GNU Autoconf 2.69. Invocation command line was
$ $0 $@
@@ -2746,8 +2746,8 @@ test "$program_suffix" != NONE &&
ac_script='s/[\\$]/&&/g;s/;s,x,x,$//'
program_transform_name=`$as_echo "$program_transform_name" | sed "$ac_script"`
-# expand $ac_aux_dir to an absolute path
-am_aux_dir=`cd $ac_aux_dir && pwd`
+# Expand $ac_aux_dir to an absolute path.
+am_aux_dir=`cd "$ac_aux_dir" && pwd`
if test x"${MISSING+set}" != xset; then
case $am_aux_dir in
@@ -3060,7 +3060,7 @@ fi
# Define the identity of the package.
PACKAGE='wallet'
- VERSION='1.1'
+ VERSION='1.2'
cat >>confdefs.h <<_ACEOF
@@ -5142,6 +5142,7 @@ fi
+
# Check whether --with-wallet-server was given.
if test "${with_wallet_server+set}" = set; then :
withval=$with_wallet_server; if test x"$withval" != xno && test x"$withval" != xyes; then :
@@ -12744,7 +12745,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
-This file was extended by wallet $as_me 1.1, which was
+This file was extended by wallet $as_me 1.2, which was
generated by GNU Autoconf 2.69. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@@ -12810,7 +12811,7 @@ _ACEOF
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
ac_cs_version="\\
-wallet config.status 1.1
+wallet config.status 1.2
configured by $0, generated by GNU Autoconf 2.69,
with options \\"\$ac_cs_config\\"
diff --git a/configure.ac b/configure.ac
index 7bd0585..4efc5d7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -7,7 +7,7 @@ dnl
dnl See LICENSE for licensing terms.
AC_PREREQ([2.64])
-AC_INIT([wallet], [1.1], [eagle@eyrie.org])
+AC_INIT([wallet], [1.2], [eagle@eyrie.org])
AC_CONFIG_AUX_DIR([build-aux])
AC_CONFIG_LIBOBJ_DIR([portable])
AC_CONFIG_MACRO_DIR([m4])
@@ -24,6 +24,7 @@ AM_PROG_CC_C_O
m4_ifdef([AM_PROG_AR], [AM_PROG_AR])
AC_PROG_INSTALL
AC_PROG_RANLIB
+AC_PROG_MKDIR_P
dnl Allow modification of the default wallet port, and setting a default
dnl wallet server when none is defined in krb5.conf.
diff --git a/contrib/wallet-rekey-periodic.8 b/contrib/wallet-rekey-periodic.8
index 0899a6e..e4c927b 100644
--- a/contrib/wallet-rekey-periodic.8
+++ b/contrib/wallet-rekey-periodic.8
@@ -1,4 +1,4 @@
-.\" Automatically generated by Pod::Man 2.27 (Pod::Simple 3.28)
+.\" Automatically generated by Pod::Man 2.28 (Pod::Simple 3.28)
.\"
.\" Standard preamble:
.\" ========================================================================
@@ -133,7 +133,7 @@
.\" ========================================================================
.\"
.IX Title "WALLET-REKEY-PERIODIC 8"
-.TH WALLET-REKEY-PERIODIC 8 "2014-07-16" "1.1" "wallet"
+.TH WALLET-REKEY-PERIODIC 8 "2014-12-08" "1.2" "wallet"
.\" For nroff, turn off justification. Always turn off hyphenation; it makes
.\" way too many mistakes in technical documents.
.if n .ad l
diff --git a/contrib/wallet-summary.8 b/contrib/wallet-summary.8
index 206e24a..0e04384 100644
--- a/contrib/wallet-summary.8
+++ b/contrib/wallet-summary.8
@@ -1,4 +1,4 @@
-.\" Automatically generated by Pod::Man 2.27 (Pod::Simple 3.28)
+.\" Automatically generated by Pod::Man 2.28 (Pod::Simple 3.28)
.\"
.\" Standard preamble:
.\" ========================================================================
@@ -133,7 +133,7 @@
.\" ========================================================================
.\"
.IX Title "WALLET-SUMMARY 8"
-.TH WALLET-SUMMARY 8 "2014-07-16" "1.1" "wallet"
+.TH WALLET-SUMMARY 8 "2014-12-08" "1.2" "wallet"
.\" For nroff, turn off justification. Always turn off hyphenation; it makes
.\" way too many mistakes in technical documents.
.if n .ad l
diff --git a/contrib/wallet-unknown-hosts.8 b/contrib/wallet-unknown-hosts.8
index 29f21ed..d61587f 100644
--- a/contrib/wallet-unknown-hosts.8
+++ b/contrib/wallet-unknown-hosts.8
@@ -1,4 +1,4 @@
-.\" Automatically generated by Pod::Man 2.27 (Pod::Simple 3.28)
+.\" Automatically generated by Pod::Man 2.28 (Pod::Simple 3.28)
.\"
.\" Standard preamble:
.\" ========================================================================
@@ -133,7 +133,7 @@
.\" ========================================================================
.\"
.IX Title "WALLET-UNKNOWN-HOSTS 8"
-.TH WALLET-UNKNOWN-HOSTS 8 "2014-07-16" "1.1" "wallet"
+.TH WALLET-UNKNOWN-HOSTS 8 "2014-12-08" "1.2" "wallet"
.\" For nroff, turn off justification. Always turn off hyphenation; it makes
.\" way too many mistakes in technical documents.
.if n .ad l
diff --git a/perl/lib/Wallet/Admin.pm b/perl/lib/Wallet/Admin.pm
index d32a0c2..8120e9c 100644
--- a/perl/lib/Wallet/Admin.pm
+++ b/perl/lib/Wallet/Admin.pm
@@ -126,6 +126,10 @@ sub default_data {
# types default rows.
my @record = ([ qw/ty_name ty_class/ ],
[ 'duo', 'Wallet::Object::Duo' ],
+ [ 'duo-ldap', 'Wallet::Object::Duo::LDAPProxy' ],
+ [ 'duo-pam', 'Wallet::Object::Duo::PAM' ],
+ [ 'duo-radius', 'Wallet::Object::Duo::RadiusProxy' ],
+ [ 'duo-rdp', 'Wallet::Object::Duo::RDP' ],
[ 'file', 'Wallet::Object::File' ],
[ 'keytab', 'Wallet::Object::Keytab' ],
[ 'wa-keyring', 'Wallet::Object::WAKeyring' ]);
diff --git a/perl/lib/Wallet/Config.pm b/perl/lib/Wallet/Config.pm
index 527658c..2eb57f9 100644
--- a/perl/lib/Wallet/Config.pm
+++ b/perl/lib/Wallet/Config.pm
@@ -217,9 +217,9 @@ our $DUO_KEY_FILE;
=item DUO_TYPE
-The type of integration to create. Currently, only one type of integration
-can be created by one wallet configuration. This restriction may be relaxed
-in the future. The default value is C<unix> to create UNIX integrations.
+The type of integration to create. The default value is C<unix> to create
+UNIX integrations, since this was the first integration created and users
+may rely on it to still be the default.
=cut
diff --git a/perl/lib/Wallet/Object/Base.pm b/perl/lib/Wallet/Object/Base.pm
index a6a78bf..bdd61fb 100644
--- a/perl/lib/Wallet/Object/Base.pm
+++ b/perl/lib/Wallet/Object/Base.pm
@@ -187,7 +187,7 @@ sub log_set {
}
my %fields = map { $_ => 1 }
qw(owner acl_get acl_store acl_show acl_destroy acl_flags expires
- comment flags type_data);
+ comment flags type_data name);
unless ($fields{$field}) {
die "invalid history field $field";
}
diff --git a/perl/lib/Wallet/Object/Duo.pm b/perl/lib/Wallet/Object/Duo.pm
index 6edc4fa..d08294b 100644
--- a/perl/lib/Wallet/Object/Duo.pm
+++ b/perl/lib/Wallet/Object/Duo.pm
@@ -1,4 +1,4 @@
-# Wallet::Object::Duo -- Duo integration object implementation for the wallet.
+# Wallet::Object::Duo -- Base Duo object implementation for the wallet
#
# Written by Russ Allbery <eagle@eyrie.org>
# Copyright 2014
@@ -29,7 +29,7 @@ use Wallet::Object::Base;
# 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.01';
+$VERSION = '0.02';
##############################################################################
# Core methods
@@ -41,7 +41,9 @@ sub attr_show {
my $output = '';
my $key;
eval {
- my %search = (du_name => $self->{name});
+ my %search = (du_name => $self->{name},
+ du_type => $self->{type},
+ );
my $row = $self->{schema}->resultset ('Duo')->find (\%search);
$key = $row->get_column ('du_key');
};
@@ -84,7 +86,7 @@ sub new {
# great here since we don't have a way to communicate the error back to the
# caller.
sub create {
- my ($class, $type, $name, $schema, $creator, $host, $time) = @_;
+ my ($class, $type, $name, $schema, $creator, $host, $time, $duo_type) = @_;
# We have to have a Duo integration key file set.
if (not $Wallet::Config::DUO_KEY_FILE) {
@@ -104,10 +106,11 @@ sub create {
# Create the object in Duo.
require Net::Duo::Admin::Integration;
+ $duo_type ||= $Wallet::Config::DUO_TYPE;
my %data = (
- name => "$name ($Wallet::Config::DUO_TYPE)",
+ name => "$name ($duo_type)",
notes => 'Managed by wallet',
- type => $Wallet::Config::DUO_TYPE,
+ type => $duo_type,
);
my $integration = Net::Duo::Admin::Integration->create ($duo, \%data);
@@ -121,6 +124,7 @@ sub create {
eval {
my %record = (
du_name => $name,
+ du_type => $type,
du_key => $integration->integration_key,
);
$self->{schema}->resultset ('Duo')->create (\%record);
@@ -147,7 +151,9 @@ sub destroy {
my $schema = $self->{schema};
my $guard = $schema->txn_scope_guard;
eval {
- my %search = (du_name => $self->{name});
+ my %search = (du_name => $self->{name},
+ du_type => $self->{type},
+ );
my $row = $schema->resultset ('Duo')->find (\%search);
my $key = $row->get_column ('du_key');
my $int = Net::Duo::Admin::Integration->new ($self->{duo}, $key);
@@ -178,7 +184,9 @@ sub get {
# Retrieve the integration from Duo.
my $key;
eval {
- my %search = (du_name => $self->{name});
+ my %search = (du_name => $self->{name},
+ du_type => $self->{type},
+ );
my $row = $self->{schema}->resultset ('Duo')->find (\%search);
$key = $row->get_column ('du_key');
};
@@ -194,10 +202,10 @@ sub get {
my $config = $json->decode (scalar slurp $Wallet::Config::DUO_KEY_FILE);
# Construct the returned file.
- my $output = "[duo]\n";
- $output .= "ikey = $key\n";
- $output .= 'skey = ' . $integration->secret_key . "\n";
- $output .= "host = $config->{api_hostname}\n";
+ my $output;
+ $output .= "Integration key: $key\n";
+ $output .= 'Secret key: ' . $integration->secret_key . "\n";
+ $output .= "Host: $config->{api_hostname}\n";
# Log the action and return.
$self->log_action ('get', $user, $host, $time);
@@ -234,12 +242,11 @@ create a Duo integration, return a configuration file containing the key
and API information for that integration, and delete the integration from
Duo when the wallet object is destroyed.
-Currently, only one configured integration type can be managed by the
-wallet, and the integration information is always returned in the
-configuration file format expected by the Duo UNIX integration. The
-results of retrieving this object will be text, suitable for putting in
-the UNIX integration configuration file, containing the integration key,
-secret key, and admin hostname for that integration.
+Usually you will want to use one of the subclasses of this module, which
+override the output to give you a configuration fragment suited for a
+specific application type. However, you can always use this module for
+generic integrations where you don't mind massaging the output into the
+configuration for the application using Duo.
This object can be retrieved repeatedly without changing the secret key,
matching Duo's native behavior with integrations. To change the keys of
@@ -258,7 +265,7 @@ implementation.
=over 4
-=item create(TYPE, NAME, DBH, PRINCIPAL, HOSTNAME [, DATETIME])
+=item create(TYPE, NAME, DBH, PRINCIPAL, HOSTNAME [, DATETIME, INTEGRATION_TYPE])
This is a class method and should be called on the Wallet::Object::Duo
class. It creates a new object with the given TYPE and NAME (TYPE is
@@ -272,9 +279,9 @@ time is used.
When a new Duo integration object is created, a new integration will be
created in the configured Duo account and the integration key will be
stored in the wallet object. If the integration already exists, create()
-will fail. The new integration's type is controlled by the DUO_TYPE
-configuration variable, which defaults to C<unix>. See L<Wallet::Config>
-for more information.
+will fail. If an integration type isn't given, the new integration's type
+is controlled by the DUO_TYPE configuration variable, which defaults to
+C<unix>. See L<Wallet::Config> for more information.
If create() fails, it throws an exception.
@@ -314,9 +321,6 @@ isn't given, the current time is used.
=head1 LIMITATIONS
Only one Duo account is supported for a given wallet implementation.
-Currently, only one Duo integration type is supported as well. Further
-development should expand the available integration types, possibly as
-additional wallet object types.
=head1 SEE ALSO
diff --git a/perl/lib/Wallet/Object/Duo/LDAPProxy.pm b/perl/lib/Wallet/Object/Duo/LDAPProxy.pm
new file mode 100644
index 0000000..74ff43c
--- /dev/null
+++ b/perl/lib/Wallet/Object/Duo/LDAPProxy.pm
@@ -0,0 +1,202 @@
+# Wallet::Object::Duo::LDAPProxy -- Duo auth proxy integration for LDAP
+#
+# Written by Jon Robertson <jonrober@stanford.edu>
+# Copyright 2014
+# The Board of Trustees of the Leland Stanford Junior University
+#
+# See LICENSE for licensing terms.
+
+##############################################################################
+# Modules and declarations
+##############################################################################
+
+package Wallet::Object::Duo::LDAPProxy;
+require 5.006;
+
+use strict;
+use warnings;
+use vars qw(@ISA $VERSION);
+
+use JSON;
+use Net::Duo::Admin;
+use Net::Duo::Admin::Integration;
+use Perl6::Slurp qw(slurp);
+use Wallet::Config ();
+use Wallet::Object::Duo;
+
+@ISA = qw(Wallet::Object::Duo);
+
+# 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.01';
+
+##############################################################################
+# Core methods
+##############################################################################
+
+# Override create to provide the specific Duo integration type that will be
+# used in the remote Duo record.
+sub create {
+ my ($class, $type, $name, $schema, $creator, $host, $time) = @_;
+
+ $time ||= time;
+ my $self = $class->SUPER::create ($type, $name, $schema, $creator, $host,
+ $time, 'ldapproxy');
+ return $self;
+}
+
+# Override get to output the data in a specific format used for Duo LDAP
+# integration
+sub get {
+ my ($self, $user, $host, $time) = @_;
+ $time ||= time;
+
+ # Check that the object isn't locked.
+ my $id = $self->{type} . ':' . $self->{name};
+ if ($self->flag_check ('locked')) {
+ $self->error ("cannot get $id: object is locked");
+ return;
+ }
+
+ # Retrieve the integration from Duo.
+ my $key;
+ eval {
+ my %search = (du_name => $self->{name});
+ my $row = $self->{schema}->resultset ('Duo')->find (\%search);
+ $key = $row->get_column ('du_key');
+ };
+ if ($@) {
+ $self->error ($@);
+ return;
+ }
+ my $integration = Net::Duo::Admin::Integration->new ($self->{duo}, $key);
+
+ # We also need the admin server name, which we can get from the Duo object
+ # configuration with a bit of JSON decoding.
+ my $json = JSON->new->utf8 (1)->relaxed (1);
+ my $config = $json->decode (scalar slurp $Wallet::Config::DUO_KEY_FILE);
+
+ # Construct the returned file.
+ my $output = "[ldap_server_challenge]\n";
+ $output .= "ikey = $key\n";
+ $output .= 'skey = ' . $integration->secret_key . "\n";
+ $output .= "api_host = $config->{api_hostname}\n";
+
+ # Log the action and return.
+ $self->log_action ('get', $user, $host, $time);
+ return $output;
+}
+
+1;
+__END__
+
+##############################################################################
+# Documentation
+##############################################################################
+
+=for stopwords
+Allbery Duo integration DBH keytab LDAP auth
+
+=head1 NAME
+
+Wallet::Object::Duo::LDAPProxy -- Duo auth proxy integration for LDAP
+
+=head1 SYNOPSIS
+
+ my @name = qw(duo-ldap host.example.com);
+ my @trace = ($user, $host, time);
+ my $object = Wallet::Object::Duo::LDAPProxy->create (@name, $schema, @trace);
+ my $config = $object->get (@trace);
+ $object->destroy (@trace);
+
+=head1 DESCRIPTION
+
+Wallet::Object::Duo::LDAPProxy is a representation of Duo
+integrations with the wallet, specifically to output Duo integrations
+in a format that can easily be pulled into configuring the Duo
+Authentication Proxy for LDAP. It implements the wallet object API
+and provides the necessary glue to create a Duo integration, return a
+configuration file containing the key and API information for that
+integration, and delete the integration from Duo when the wallet object
+is destroyed.
+
+The integration information is always returned in the configuration file
+format expected by the Authentication Proxy for Duo in configuring it
+for LDAP.
+
+This object can be retrieved repeatedly without changing the secret key,
+matching Duo's native behavior with integrations. To change the keys of
+the integration, delete it and recreate it.
+
+To use this object, at least one configuration parameter must be set. See
+L<Wallet::Config> for details on supported configuration parameters and
+information about how to set wallet configuration.
+
+=head1 METHODS
+
+This object mostly inherits from Wallet::Object::Duo. See the
+documentation for that class for all generic methods. Below are only
+those methods that are overridden or behave specially for this
+implementation.
+
+=over 4
+
+=item create(TYPE, NAME, DBH, PRINCIPAL, HOSTNAME [, DATETIME])
+
+This will override the Wallet::Object::Duo class with the information
+needed to create a specific integration type in Duo. It creates a new
+object with the given TYPE and NAME (TYPE is normally C<duo-ldap> and
+must be for the rest of the wallet system to use the right class, but
+this module doesn't check for ease of subclassing), using DBH as the
+handle to the wallet metadata database. PRINCIPAL, HOSTNAME, and
+DATETIME are stored as history information. PRINCIPAL should be the
+user who is creating the object. If DATETIME isn't given, the current
+time is used.
+
+When a new Duo integration object is created, a new integration will be
+created in the configured Duo account and the integration key will be
+stored in the wallet object. If the integration already exists, create()
+will fail.
+
+If create() fails, it throws an exception.
+
+=item get(PRINCIPAL, HOSTNAME [, DATETIME])
+
+Retrieves the configuration information for the Duo integration and
+returns that information in the format expected by the configuration file
+for the Duo UNIX integration. Returns undef on failure. The caller
+should call error() to get the error message if get() returns undef.
+
+The returned configuration look look like:
+
+ [ldap_server_challenge]
+ ikey = <integration-key>
+ skey = <secret-key>
+ api_host = <api-hostname>
+
+The C<host> parameter will be taken from the configuration file pointed
+to by the DUO_KEY_FILE configuration variable.
+
+PRINCIPAL, HOSTNAME, and DATETIME are stored as history information.
+PRINCIPAL should be the user who is downloading the keytab. If DATETIME
+isn't given, the current time is used.
+
+=back
+
+=head1 LIMITATIONS
+
+Only one Duo account is supported for a given wallet implementation.
+
+=head1 SEE ALSO
+
+Net::Duo(3), Wallet::Config(3), Wallet::Object::Duo(3), 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 AUTHORS
+
+Jon Robertson <jonrober@stanford.edu>
+
+=cut
diff --git a/perl/lib/Wallet/Object/Duo/PAM.pm b/perl/lib/Wallet/Object/Duo/PAM.pm
new file mode 100644
index 0000000..6f90ba1
--- /dev/null
+++ b/perl/lib/Wallet/Object/Duo/PAM.pm
@@ -0,0 +1,205 @@
+# Wallet::Object::Duo::PAM -- Duo PAM int. object implementation for wallet
+#
+# Written by Russ Allbery <eagle@eyrie.org>
+# Jon Robertson <jonrober@stanford.edu>
+# Copyright 2014
+# The Board of Trustees of the Leland Stanford Junior University
+#
+# See LICENSE for licensing terms.
+
+##############################################################################
+# Modules and declarations
+##############################################################################
+
+package Wallet::Object::Duo::PAM;
+require 5.006;
+
+use strict;
+use warnings;
+use vars qw(@ISA $VERSION);
+
+use JSON;
+use Net::Duo::Admin;
+use Net::Duo::Admin::Integration;
+use Perl6::Slurp qw(slurp);
+use Wallet::Config ();
+use Wallet::Object::Duo;
+
+@ISA = qw(Wallet::Object::Duo);
+
+# 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.01';
+
+##############################################################################
+# Core methods
+##############################################################################
+
+# Override create to provide the specific Duo integration type that will be
+# used in the remote Duo record.
+sub create {
+ my ($class, $type, $name, $schema, $creator, $host, $time) = @_;
+
+ $time ||= time;
+ my $self = $class->SUPER::create ($type, $name, $schema, $creator, $host,
+ $time, 'unix');
+ return $self;
+}
+
+# Override get to output the data in a specific format used by Duo's PAM
+# module.
+sub get {
+ my ($self, $user, $host, $time) = @_;
+ $time ||= time;
+
+ # Check that the object isn't locked.
+ my $id = $self->{type} . ':' . $self->{name};
+ if ($self->flag_check ('locked')) {
+ $self->error ("cannot get $id: object is locked");
+ return;
+ }
+
+ # Retrieve the integration from Duo.
+ my $key;
+ eval {
+ my %search = (du_name => $self->{name});
+ my $row = $self->{schema}->resultset ('Duo')->find (\%search);
+ $key = $row->get_column ('du_key');
+ };
+ if ($@) {
+ $self->error ($@);
+ return;
+ }
+ my $integration = Net::Duo::Admin::Integration->new ($self->{duo}, $key);
+
+ # We also need the admin server name, which we can get from the Duo object
+ # configuration with a bit of JSON decoding.
+ my $json = JSON->new->utf8 (1)->relaxed (1);
+ my $config = $json->decode (scalar slurp $Wallet::Config::DUO_KEY_FILE);
+
+ # Construct the returned file.
+ my $output = "[duo]\n";
+ $output .= "ikey = $key\n";
+ $output .= 'skey = ' . $integration->secret_key . "\n";
+ $output .= "host = $config->{api_hostname}\n";
+
+ # Log the action and return.
+ $self->log_action ('get', $user, $host, $time);
+ return $output;
+}
+
+1;
+__END__
+
+##############################################################################
+# Documentation
+##############################################################################
+
+=for stopwords
+Allbery Duo integration DBH keytab
+
+=head1 NAME
+
+Wallet::Object::Duo::PAM -- Duo PAM int. object implementation for wallet
+
+=head1 SYNOPSIS
+
+ my @name = qw(duo-pam host.example.com);
+ my @trace = ($user, $host, time);
+ my $object = Wallet::Object::Duo::PAM->create (@name, $schema, @trace);
+ my $config = $object->get (@trace);
+ $object->destroy (@trace);
+
+=head1 DESCRIPTION
+
+Wallet::Object::Duo::PAM is a representation of Duo integrations with
+the wallet, specifically to output Duo integrations in a format that
+can easily be pulled into configuring the Duo PAM interface. It
+implements the wallet object API and provides the necessary glue to
+create a Duo integration, return a configuration file containing the key
+and API information for that integration, and delete the integration from
+Duo when the wallet object is destroyed.
+
+The integration information is always returned in the configuration file
+format expected by the Duo UNIX integration. The results of retrieving
+this object will be text, suitable for putting in the UNIX integration
+configuration file, containing the integration key, secret key, and admin
+hostname for that integration.
+
+This object can be retrieved repeatedly without changing the secret key,
+matching Duo's native behavior with integrations. To change the keys of
+the integration, delete it and recreate it.
+
+To use this object, at least one configuration parameter must be set. See
+L<Wallet::Config> for details on supported configuration parameters and
+information about how to set wallet configuration.
+
+=head1 METHODS
+
+This object mostly inherits from Wallet::Object::Duo. See the
+documentation for that class for all generic methods. Below are only
+those methods that are overridden or behave specially for this
+implementation.
+
+=over 4
+
+=item create(TYPE, NAME, DBH, PRINCIPAL, HOSTNAME [, DATETIME])
+
+This will override the Wallet::Object::Duo class with the information
+needed to create a specific integration type in Duo. It creates a new
+object with the given TYPE and NAME (TYPE is normally C<duo-pam> and must
+be for the rest of the wallet system to use the right class, but this
+module doesn't check for ease of subclassing), using DBH as the handle
+to the wallet metadata database. PRINCIPAL, HOSTNAME, and DATETIME are
+stored as history information. PRINCIPAL should be the user who is
+creating the object. If DATETIME isn't given, the current time is
+used.
+
+When a new Duo integration object is created, a new integration will be
+created in the configured Duo account and the integration key will be
+stored in the wallet object. If the integration already exists, create()
+will fail.
+
+If create() fails, it throws an exception.
+
+=item get(PRINCIPAL, HOSTNAME [, DATETIME])
+
+Retrieves the configuration information for the Duo integration and
+returns that information in the format expected by the configuration file
+for the Duo UNIX integration. Returns undef on failure. The caller
+should call error() to get the error message if get() returns undef.
+
+The returned configuration look look like:
+
+ [duo]
+ ikey = <integration-key>
+ skey = <secret-key>
+ host = <api-hostname>
+
+The C<host> parameter will be taken from the configuration file pointed
+to by the DUO_KEY_FILE configuration variable.
+
+PRINCIPAL, HOSTNAME, and DATETIME are stored as history information.
+PRINCIPAL should be the user who is downloading the keytab. If DATETIME
+isn't given, the current time is used.
+
+=back
+
+=head1 LIMITATIONS
+
+Only one Duo account is supported for a given wallet implementation.
+
+=head1 SEE ALSO
+
+Net::Duo(3), Wallet::Config(3), Wallet::Object::Duo(3), 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 AUTHORS
+
+Russ Allbery <eagle@eyrie.org>
+Jon Robertson <eagle@eyrie.org>
+
+=cut
diff --git a/perl/lib/Wallet/Object/Duo/RDP.pm b/perl/lib/Wallet/Object/Duo/RDP.pm
new file mode 100644
index 0000000..2e975fc
--- /dev/null
+++ b/perl/lib/Wallet/Object/Duo/RDP.pm
@@ -0,0 +1,204 @@
+# Wallet::Object::Duo::RDP -- Duo RDP int. object implementation for wallet
+#
+# Written by Russ Allbery <eagle@eyrie.org>
+# Jon Robertson <jonrober@stanford.edu>
+# Copyright 2014
+# The Board of Trustees of the Leland Stanford Junior University
+#
+# See LICENSE for licensing terms.
+
+##############################################################################
+# Modules and declarations
+##############################################################################
+
+package Wallet::Object::Duo::RDP;
+require 5.006;
+
+use strict;
+use warnings;
+use vars qw(@ISA $VERSION);
+
+use JSON;
+use Net::Duo::Admin;
+use Net::Duo::Admin::Integration;
+use Perl6::Slurp qw(slurp);
+use Wallet::Config ();
+use Wallet::Object::Duo;
+
+@ISA = qw(Wallet::Object::Duo);
+
+# 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.01';
+
+##############################################################################
+# Core methods
+##############################################################################
+
+# Override create to provide the specific Duo integration type that will be
+# used in the remote Duo record.
+sub create {
+ my ($class, $type, $name, $schema, $creator, $host, $time) = @_;
+
+ $time ||= time;
+ my $self = $class->SUPER::create ($type, $name, $schema, $creator, $host,
+ $time, 'rdp');
+ return $self;
+}
+
+# Override get to output the data in a specific format used by Duo's RDP
+# module.
+sub get {
+ my ($self, $user, $host, $time) = @_;
+ $time ||= time;
+
+ # Check that the object isn't locked.
+ my $id = $self->{type} . ':' . $self->{name};
+ if ($self->flag_check ('locked')) {
+ $self->error ("cannot get $id: object is locked");
+ return;
+ }
+
+ # Retrieve the integration from Duo.
+ my $key;
+ eval {
+ my %search = (du_name => $self->{name});
+ my $row = $self->{schema}->resultset ('Duo')->find (\%search);
+ $key = $row->get_column ('du_key');
+ };
+ if ($@) {
+ $self->error ($@);
+ return;
+ }
+ my $integration = Net::Duo::Admin::Integration->new ($self->{duo}, $key);
+
+ # We also need the admin server name, which we can get from the Duo object
+ # configuration with a bit of JSON decoding.
+ my $json = JSON->new->utf8 (1)->relaxed (1);
+ my $config = $json->decode (scalar slurp $Wallet::Config::DUO_KEY_FILE);
+
+ # Construct the returned file.
+ my $output;
+ $output .= "Integration key: $key\n";
+ $output .= 'Secret key: ' . $integration->secret_key . "\n";
+ $output .= "Host: $config->{api_hostname}\n";
+
+ # Log the action and return.
+ $self->log_action ('get', $user, $host, $time);
+ return $output;
+}
+
+1;
+__END__
+
+##############################################################################
+# Documentation
+##############################################################################
+
+=for stopwords
+Allbery Duo integration DBH keytab RDP
+
+=head1 NAME
+
+Wallet::Object::Duo::RDP -- Duo RDP int. object implementation for wallet
+
+=head1 SYNOPSIS
+
+ my @name = qw(duo-rdp host.example.com);
+ my @trace = ($user, $host, time);
+ my $object = Wallet::Object::Duo::RDP->create (@name, $schema, @trace);
+ my $config = $object->get (@trace);
+ $object->destroy (@trace);
+
+=head1 DESCRIPTION
+
+Wallet::Object::Duo::RDP is a representation of Duo integrations with
+the wallet, specifically to output Duo integrations to set up an RDP
+integration. This can be used to set up remote logins, or all Windows
+logins period if so selected in Duo's software. It implements the
+wallet object API and provides the necessary glue to create a Duo
+integration, return a configuration file containing the key and API
+information for that integration, and delete the integration from Duo
+when the wallet object is destroyed.
+
+Because the Duo RDP software is configured by a GUI, the information
+returned for a get operation is a simple set that's readable but not
+useful for directly plugging into a config file. The values would need
+to be cut and pasted into the GUI.
+
+This object can be retrieved repeatedly without changing the secret key,
+matching Duo's native behavior with integrations. To change the keys of
+the integration, delete it and recreate it.
+
+To use this object, at least one configuration parameter must be set. See
+L<Wallet::Config> for details on supported configuration parameters and
+information about how to set wallet configuration.
+
+=head1 METHODS
+
+This object mostly inherits from Wallet::Object::Duo. See the
+documentation for that class for all generic methods. Below are only
+those methods that are overridden or behave specially for this
+implementation.
+
+=over 4
+
+=item create(TYPE, NAME, DBH, PRINCIPAL, HOSTNAME [, DATETIME])
+
+This will override the Wallet::Object::Duo class with the information
+needed to create a specific integration type in Duo. It creates a new
+object with the given TYPE and NAME (TYPE is normally C<duo-pam> and must
+be for the rest of the wallet system to use the right class, but this
+module doesn't check for ease of subclassing), using DBH as the handle
+to the wallet metadata database. PRINCIPAL, HOSTNAME, and DATETIME are
+stored as history information. PRINCIPAL should be the user who is
+creating the object. If DATETIME isn't given, the current time is
+used.
+
+When a new Duo integration object is created, a new integration will be
+created in the configured Duo account and the integration key will be
+stored in the wallet object. If the integration already exists, create()
+will fail.
+
+If create() fails, it throws an exception.
+
+=item get(PRINCIPAL, HOSTNAME [, DATETIME])
+
+Retrieves the configuration information for the Duo integration and
+returns that information in the format expected by the configuration file
+for the Duo UNIX integration. Returns undef on failure. The caller
+should call error() to get the error message if get() returns undef.
+
+The returned configuration look look like:
+
+ Integration key: <integration-key>
+ Secret key: <secret-key>
+ Host: <api-hostname>
+
+The C<host> parameter will be taken from the configuration file pointed
+to by the DUO_KEY_FILE configuration variable.
+
+PRINCIPAL, HOSTNAME, and DATETIME are stored as history information.
+PRINCIPAL should be the user who is downloading the keytab. If DATETIME
+isn't given, the current time is used.
+
+=back
+
+=head1 LIMITATIONS
+
+Only one Duo account is supported for a given wallet implementation.
+
+=head1 SEE ALSO
+
+Net::Duo(3), Wallet::Config(3), Wallet::Object::Duo(3), 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 AUTHORS
+
+Russ Allbery <eagle@eyrie.org>
+Jon Robertson <eagle@eyrie.org>
+
+=cut
diff --git a/perl/lib/Wallet/Object/Duo/RadiusProxy.pm b/perl/lib/Wallet/Object/Duo/RadiusProxy.pm
new file mode 100644
index 0000000..faa0c2f
--- /dev/null
+++ b/perl/lib/Wallet/Object/Duo/RadiusProxy.pm
@@ -0,0 +1,204 @@
+# Wallet::Object::Duo::RadiusProxy -- Duo auth proxy integration for radius
+#
+# Written by Jon Robertson <jonrober@stanford.edu>
+# Copyright 2014
+# The Board of Trustees of the Leland Stanford Junior University
+#
+# See LICENSE for licensing terms.
+
+##############################################################################
+# Modules and declarations
+##############################################################################
+
+package Wallet::Object::Duo::RadiusProxy;
+require 5.006;
+
+use strict;
+use warnings;
+use vars qw(@ISA $VERSION);
+
+use JSON;
+use Net::Duo::Admin;
+use Net::Duo::Admin::Integration;
+use Perl6::Slurp qw(slurp);
+use Wallet::Config ();
+use Wallet::Object::Duo;
+
+@ISA = qw(Wallet::Object::Duo);
+
+# 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.01';
+
+##############################################################################
+# Core methods
+##############################################################################
+
+# Override create to provide the specific Duo integration type that will be
+# used in the remote Duo record.
+sub create {
+ my ($class, $type, $name, $schema, $creator, $host, $time) = @_;
+
+ $time ||= time;
+ my $self = $class->SUPER::create ($type, $name, $schema, $creator, $host,
+ $time, 'radius');
+ return $self;
+}
+
+# Override get to output the data in a specific format used for Duo radius
+# integration
+sub get {
+ my ($self, $user, $host, $time) = @_;
+ $time ||= time;
+
+ # Check that the object isn't locked.
+ my $id = $self->{type} . ':' . $self->{name};
+ if ($self->flag_check ('locked')) {
+ $self->error ("cannot get $id: object is locked");
+ return;
+ }
+
+ # Retrieve the integration from Duo.
+ my $key;
+ eval {
+ my %search = (du_name => $self->{name});
+ my $row = $self->{schema}->resultset ('Duo')->find (\%search);
+ $key = $row->get_column ('du_key');
+ };
+ if ($@) {
+ $self->error ($@);
+ return;
+ }
+ my $integration = Net::Duo::Admin::Integration->new ($self->{duo}, $key);
+
+ # We also need the admin server name, which we can get from the Duo object
+ # configuration with a bit of JSON decoding.
+ my $json = JSON->new->utf8 (1)->relaxed (1);
+ my $config = $json->decode (scalar slurp $Wallet::Config::DUO_KEY_FILE);
+
+ # Construct the returned file.
+ my $output = "[radius_server_challenge]\n";
+ $output .= "ikey = $key\n";
+ $output .= 'skey = ' . $integration->secret_key . "\n";
+ $output .= "api_host = $config->{api_hostname}\n";
+ $output .= "client = radius_client\n";
+
+ # Log the action and return.
+ $self->log_action ('get', $user, $host, $time);
+ return $output;
+}
+
+1;
+__END__
+
+##############################################################################
+# Documentation
+##############################################################################
+
+=for stopwords
+Allbery Duo integration DBH keytab auth
+
+=head1 NAME
+
+Wallet::Object::Duo::RadiusProxy -- Duo auth proxy integration for RADIUS
+
+=head1 SYNOPSIS
+
+ my @name = qw(duo-radius host.example.com);
+ my @trace = ($user, $host, time);
+ my $object = Wallet::Object::Duo::RadiusProxy->create (@name, $schema, @trace);
+ my $config = $object->get (@trace);
+ $object->destroy (@trace);
+
+=head1 DESCRIPTION
+
+Wallet::Object::Duo::RadiusProxy is a representation of Duo
+integrations with the wallet, specifically to output Duo integrations
+in a format that can easily be pulled into configuring the Duo
+Authentication Proxy for Radius. It implements the wallet object API
+and provides the necessary glue to create a Duo integration, return a
+configuration file containing the key and API information for that
+integration, and delete the integration from Duo when the wallet object
+is destroyed.
+
+The integration information is always returned in the configuration file
+format expected by the Authentication Proxy for Duo in configuring it
+for Radius.
+
+This object can be retrieved repeatedly without changing the secret key,
+matching Duo's native behavior with integrations. To change the keys of
+the integration, delete it and recreate it.
+
+To use this object, at least one configuration parameter must be set. See
+L<Wallet::Config> for details on supported configuration parameters and
+information about how to set wallet configuration.
+
+=head1 METHODS
+
+This object mostly inherits from Wallet::Object::Duo. See the
+documentation for that class for all generic methods. Below are only
+those methods that are overridden or behave specially for this
+implementation.
+
+=over 4
+
+=item create(TYPE, NAME, DBH, PRINCIPAL, HOSTNAME [, DATETIME])
+
+This will override the Wallet::Object::Duo class with the information
+needed to create a specific integration type in Duo. It creates a new
+object with the given TYPE and NAME (TYPE is normally C<duo-radius> and
+must be for the rest of the wallet system to use the right class, but
+this module doesn't check for ease of subclassing), using DBH as the
+handle to the wallet metadata database. PRINCIPAL, HOSTNAME, and
+DATETIME are stored as history information. PRINCIPAL should be the
+user who is creating the object. If DATETIME isn't given, the current
+time is used.
+
+When a new Duo integration object is created, a new integration will be
+created in the configured Duo account and the integration key will be
+stored in the wallet object. If the integration already exists, create()
+will fail.
+
+If create() fails, it throws an exception.
+
+=item get(PRINCIPAL, HOSTNAME [, DATETIME])
+
+Retrieves the configuration information for the Duo integration and
+returns that information in the format expected by the configuration file
+for the Duo UNIX integration. Returns undef on failure. The caller
+should call error() to get the error message if get() returns undef.
+
+The returned configuration look look like:
+
+ [radius_server_challenge]
+ ikey = <integration-key>
+ skey = <secret-key>
+ api_host = <api-hostname>
+ client = radius_client
+
+The C<host> parameter will be taken from the configuration file pointed
+to by the DUO_KEY_FILE configuration variable.
+
+PRINCIPAL, HOSTNAME, and DATETIME are stored as history information.
+PRINCIPAL should be the user who is downloading the keytab. If DATETIME
+isn't given, the current time is used.
+
+=back
+
+=head1 LIMITATIONS
+
+Only one Duo account is supported for a given wallet implementation.
+
+=head1 SEE ALSO
+
+Net::Duo(3), Wallet::Config(3), Wallet::Object::Duo(3), 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 AUTHORS
+
+Jon Robertson <jonrober@stanford.edu>
+
+=cut
diff --git a/perl/lib/Wallet/Object/File.pm b/perl/lib/Wallet/Object/File.pm
index 1ff1288..226e32c 100644
--- a/perl/lib/Wallet/Object/File.pm
+++ b/perl/lib/Wallet/Object/File.pm
@@ -18,6 +18,7 @@ use warnings;
use vars qw(@ISA $VERSION);
use Digest::MD5 qw(md5_hex);
+use File::Copy qw(move);
use Wallet::Config ();
use Wallet::Object::Base;
@@ -55,6 +56,59 @@ sub file_path {
return "$Wallet::Config::FILE_BUCKET/$hash/$name";
}
+# Rename a file object. This includes renaming both the object itself, and
+# updating the file location for that object.
+sub rename {
+ my ($self, $new_name, $user, $host, $time) = @_;
+ $time ||= time;
+
+ my $old_name = $self->{name};
+ my $type = $self->{type};
+ my $schema = $self->{schema};
+ my $old_path = $self->file_path;
+
+ eval {
+
+ # Find the current object record.
+ my $guard = $schema->txn_scope_guard;
+ my %search = (ob_type => $type,
+ ob_name => $old_name);
+ my $object = $schema->resultset('Object')->find (\%search);
+ die "cannot find ${type}:${old_name}\n"
+ unless ($object and $object->ob_name eq $old_name);
+
+ # Update the object name but don't yet commit.
+ $object->ob_name ($new_name);
+
+ # Update the file to the path for the new name, and die if we can't.
+ # If the old path isn't there, then assume we haven't yet stored and
+ # keep going.
+ if ($old_path) {
+ $self->{name} = $new_name;
+ my $new_path = $self->file_path;
+ move($old_path, $new_path) or die $!;
+ }
+
+ $object->update;
+ $guard->commit;
+ };
+ if ($@) {
+ $self->{name} = $old_name;
+ $self->error ("cannot rename object $type $old_name: $!");
+ return;
+ }
+
+ eval {
+ $self->log_set ('name', $old_name, $new_name, $user, $host, $time);
+ };
+ if ($@) {
+ $self->error ("object $type $old_name was renamed but not logged: $!");
+ return 1;
+ }
+
+ return 1;
+}
+
##############################################################################
# Core methods
##############################################################################
@@ -145,7 +199,7 @@ API HOSTNAME DATETIME keytab remctld backend nul Allbery wallet-backend
my @name = qw(file mysql-lsdb)
my @trace = ($user, $host, time);
- my $object = Wallet::Object::Keytab->create (@name, $schema, @trace);
+ my $object = Wallet::Object::File->create (@name, $schema, @trace);
unless ($object->store ("the-password\n")) {
die $object->error, "\n";
}
diff --git a/perl/lib/Wallet/Policy/Stanford.pm b/perl/lib/Wallet/Policy/Stanford.pm
index 5ac29e0..a392476 100644
--- a/perl/lib/Wallet/Policy/Stanford.pm
+++ b/perl/lib/Wallet/Policy/Stanford.pm
@@ -174,6 +174,13 @@ sub _host_for_keytab {
return $host;
}
+# Map a duo-type object name to a hostname. Currently all Duo objects are
+# named just for the hostname, so this is easy.
+sub _host_for_duo {
+ my ($name) = @_;
+ return $name;
+}
+
# The default owner of host-based objects should be the host keytab and the
# NetDB ACL for that host, with one twist. If the creator of a new node is
# using a root instance, we want to require everyone managing that node be
@@ -183,8 +190,13 @@ sub default_owner {
# How to determine the host for host-based objects.
my %host_for = (
- keytab => \&_host_for_keytab,
- file => \&_host_for_file,
+ 'keytab' => \&_host_for_keytab,
+ 'file' => \&_host_for_file,
+ 'duo' => \&_host_for_duo,
+ 'duo-pam' => \&_host_for_duo,
+ 'duo-radius' => \&_host_for_duo,
+ 'duo-ldap' => \&_host_for_duo,
+ 'duo-rdp' => \&_host_for_duo,
);
# If we have a possible host mapping, see if we can use that.
@@ -368,6 +380,14 @@ sub verify_name {
}
}
+ # Check the naming conventions for all Duo object types. The object
+ # should simply be the host name for now.
+ if ($type =~ m{^duo(-\w+)?$}) {
+ if ($name !~ m{ [.] }xms) {
+ return "host name $name is not fully qualified";
+ }
+ }
+
# Success.
return;
}
diff --git a/perl/lib/Wallet/Schema.pm b/perl/lib/Wallet/Schema.pm
index cb4c93e..5b850c0 100644
--- a/perl/lib/Wallet/Schema.pm
+++ b/perl/lib/Wallet/Schema.pm
@@ -18,7 +18,7 @@ use base 'DBIx::Class::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.
-our $VERSION = '0.09';
+our $VERSION = '0.10';
__PACKAGE__->load_namespaces;
__PACKAGE__->load_components (qw/Schema::Versioned/);
diff --git a/perl/lib/Wallet/Schema/Result/Duo.pm b/perl/lib/Wallet/Schema/Result/Duo.pm
index 80a71dc..6ad61e9 100644
--- a/perl/lib/Wallet/Schema/Result/Duo.pm
+++ b/perl/lib/Wallet/Schema/Result/Duo.pm
@@ -45,9 +45,19 @@ __PACKAGE__->table("duo");
__PACKAGE__->add_columns(
"du_name",
{ data_type => "varchar", is_nullable => 0, size => 255 },
+ "du_type",
+ { data_type => "varchar", is_nullable => 0, size => 16 },
"du_key",
{ data_type => "varchar", is_nullable => 0, size => 255 },
);
-__PACKAGE__->set_primary_key("du_name");
-
+__PACKAGE__->set_primary_key("du_name", "du_type");
+
+__PACKAGE__->belongs_to(
+ 'object',
+ 'Wallet::Schema::Result::Object',
+ {
+ 'foreign.ob_type' => 'self.du_type',
+ 'foreign.ob_name' => 'self.du_name',
+ },
+ );
1;
diff --git a/perl/lib/Wallet/Server.pm b/perl/lib/Wallet/Server.pm
index 95fd4e6..f6ea342 100644
--- a/perl/lib/Wallet/Server.pm
+++ b/perl/lib/Wallet/Server.pm
@@ -244,6 +244,52 @@ sub autocreate {
return 1;
}
+# Rename an object. We validate that the new name also falls within naming
+# constraints, then need to change all references to that. If any updates
+# fail, we'll revert the entire commit.
+sub rename {
+ my ($self, $type, $name, $new_name) = @_;
+
+ my $schema = $self->{schema};
+ my $user = $self->{user};
+ my $host = $self->{host};
+
+ # Currently we only can rename file objects.
+ if ($type ne 'file') {
+ $self->error ('rename is only supported for file objects');
+ return;
+ }
+
+ # Validate the new name.
+ if (defined (&Wallet::Config::verify_name)) {
+ my $error = Wallet::Config::verify_name ($type, $new_name, $user);
+ if ($error) {
+ $self->error ("${type}:${name} rejected: $error");
+ return;
+ }
+ }
+
+ # Get the object and error if it does not already exist.
+ my $class = $self->type_mapping ($type);
+ unless ($class) {
+ $self->error ("unknown object type $type");
+ return;
+ }
+ my $object = eval { $class->new ($type, $name, $schema) };
+ if ($@) {
+ $self->error ($@);
+ return;
+ }
+
+ # Rename the object.
+ eval { $object->rename ($new_name, $schema, $user, $host) };
+ if ($@) {
+ $self->error ($@);
+ return;
+ }
+ return $object;
+}
+
# Given the name and type of an object, returns a Perl object representing it
# or returns undef and sets the internal error.
sub retrieve {
diff --git a/perl/sql/Wallet-Schema-0.09-0.10-MySQL.sql b/perl/sql/Wallet-Schema-0.09-0.10-MySQL.sql
new file mode 100644
index 0000000..3c54c6d
--- /dev/null
+++ b/perl/sql/Wallet-Schema-0.09-0.10-MySQL.sql
@@ -0,0 +1,14 @@
+-- Convert schema 'sql/Wallet-Schema-0.09-MySQL.sql' to 'Wallet::Schema v0.10':;
+
+BEGIN;
+
+ALTER TABLE duo DROP PRIMARY KEY,
+ ADD COLUMN du_type varchar(16) NOT NULL,
+ ADD INDEX duo_idx_du_type_du_name (du_type, du_name),
+ ADD PRIMARY KEY (du_name, du_type),
+ ADD CONSTRAINT duo_fk_du_type_du_name FOREIGN KEY (du_type, du_name) REFERENCES objects (ob_type, ob_name),
+ ENGINE=InnoDB;
+
+
+COMMIT;
+
diff --git a/perl/sql/Wallet-Schema-0.09-0.10-PostgreSQL.sql b/perl/sql/Wallet-Schema-0.09-0.10-PostgreSQL.sql
new file mode 100644
index 0000000..c69e6a5
--- /dev/null
+++ b/perl/sql/Wallet-Schema-0.09-0.10-PostgreSQL.sql
@@ -0,0 +1,18 @@
+-- Convert schema 'sql/Wallet-Schema-0.09-PostgreSQL.sql' to 'sql/Wallet-Schema-0.10-PostgreSQL.sql':;
+
+BEGIN;
+
+ALTER TABLE duo DROP CONSTRAINT duo_pkey;
+
+ALTER TABLE duo ADD COLUMN du_type character varying(16) NOT NULL;
+
+CREATE INDEX duo_idx_du_type_du_name on duo (du_type, du_name);
+
+ALTER TABLE duo ADD PRIMARY KEY (du_name, du_type);
+
+ALTER TABLE duo ADD CONSTRAINT duo_fk_du_type_du_name FOREIGN KEY (du_type, du_name)
+ REFERENCES objects (ob_type, ob_name) DEFERRABLE;
+
+
+COMMIT;
+
diff --git a/perl/sql/Wallet-Schema-0.09-0.10-SQLite.sql b/perl/sql/Wallet-Schema-0.09-0.10-SQLite.sql
new file mode 100644
index 0000000..5feb89f
--- /dev/null
+++ b/perl/sql/Wallet-Schema-0.09-0.10-SQLite.sql
@@ -0,0 +1,26 @@
+-- Convert schema 'sql/Wallet-Schema-0.09-SQLite.sql' to 'sql/Wallet-Schema-0.10-SQLite.sql':;
+
+BEGIN;
+
+-- Back up Duo data to a temp table. SQLite has limited ALTER TABLE support,
+-- so we need to do this to alter the keys on the table.
+CREATE TEMPORARY TABLE duo_backup (
+ du_name varchar(255) NOT NULL,
+ du_key varchar(255) NOT NULL,
+ PRIMARY KEY (du_name)
+);
+INSERT INTO duo_backup SELECT du_name,du_key FROM duo;
+DROP TABLE duo;
+
+-- Create the new Duo table and move the old data into it.
+CREATE TABLE duo (
+ du_name varchar(255) NOT NULL,
+ du_type varchar(16) NOT NULL,
+ du_key varchar(255) NOT NULL,
+ PRIMARY KEY (du_name, du_type),
+ FOREIGN KEY (du_type, du_name) REFERENCES objects(ob_type, ob_name)
+);
+INSERT INTO duo SELECT du_name,du_key,'duo' FROM duo_backup;
+DROP TABLE duo_backup;
+
+COMMIT;
diff --git a/perl/sql/Wallet-Schema-0.10-MySQL.sql b/perl/sql/Wallet-Schema-0.10-MySQL.sql
new file mode 100644
index 0000000..c0b7fcc
--- /dev/null
+++ b/perl/sql/Wallet-Schema-0.10-MySQL.sql
@@ -0,0 +1,209 @@
+--
+-- Created by SQL::Translator::Producer::MySQL
+-- Created on Thu Oct 9 20:54:55 2014
+--
+SET foreign_key_checks=0;
+
+DROP TABLE IF EXISTS `acl_history`;
+
+--
+-- Table: `acl_history`
+--
+CREATE TABLE `acl_history` (
+ `ah_id` integer NOT NULL auto_increment,
+ `ah_acl` integer NOT NULL,
+ `ah_name` varchar(255) NULL,
+ `ah_action` varchar(16) NOT NULL,
+ `ah_scheme` varchar(32) NULL,
+ `ah_identifier` varchar(255) NULL,
+ `ah_by` varchar(255) NOT NULL,
+ `ah_from` varchar(255) NOT NULL,
+ `ah_on` datetime NOT NULL,
+ INDEX `acl_history_idx_ah_acl` (`ah_acl`),
+ INDEX `acl_history_idx_ah_name` (`ah_name`),
+ PRIMARY KEY (`ah_id`)
+);
+
+DROP TABLE IF EXISTS `acl_schemes`;
+
+--
+-- Table: `acl_schemes`
+--
+CREATE TABLE `acl_schemes` (
+ `as_name` varchar(32) NOT NULL,
+ `as_class` varchar(64) NULL,
+ PRIMARY KEY (`as_name`)
+) ENGINE=InnoDB;
+
+DROP TABLE IF EXISTS `acls`;
+
+--
+-- Table: `acls`
+--
+CREATE TABLE `acls` (
+ `ac_id` integer NOT NULL auto_increment,
+ `ac_name` varchar(255) NOT NULL,
+ PRIMARY KEY (`ac_id`),
+ UNIQUE `ac_name` (`ac_name`)
+) ENGINE=InnoDB;
+
+DROP TABLE IF EXISTS `enctypes`;
+
+--
+-- Table: `enctypes`
+--
+CREATE TABLE `enctypes` (
+ `en_name` varchar(255) NOT NULL,
+ PRIMARY KEY (`en_name`)
+);
+
+DROP TABLE IF EXISTS `flags`;
+
+--
+-- Table: `flags`
+--
+CREATE TABLE `flags` (
+ `fl_type` varchar(16) NOT NULL,
+ `fl_name` varchar(255) NOT NULL,
+ `fl_flag` enum('locked', 'unchanging') NOT NULL,
+ PRIMARY KEY (`fl_type`, `fl_name`, `fl_flag`)
+);
+
+DROP TABLE IF EXISTS `keytab_enctypes`;
+
+--
+-- Table: `keytab_enctypes`
+--
+CREATE TABLE `keytab_enctypes` (
+ `ke_name` varchar(255) NOT NULL,
+ `ke_enctype` varchar(255) NOT NULL,
+ PRIMARY KEY (`ke_name`, `ke_enctype`)
+);
+
+DROP TABLE IF EXISTS `keytab_sync`;
+
+--
+-- Table: `keytab_sync`
+--
+CREATE TABLE `keytab_sync` (
+ `ks_name` varchar(255) NOT NULL,
+ `ks_target` varchar(255) NOT NULL,
+ PRIMARY KEY (`ks_name`, `ks_target`)
+);
+
+DROP TABLE IF EXISTS `object_history`;
+
+--
+-- Table: `object_history`
+--
+CREATE TABLE `object_history` (
+ `oh_id` integer NOT NULL auto_increment,
+ `oh_type` varchar(16) NOT NULL,
+ `oh_name` varchar(255) NOT NULL,
+ `oh_action` varchar(16) NOT NULL,
+ `oh_field` varchar(16) NULL,
+ `oh_type_field` varchar(255) NULL,
+ `oh_old` varchar(255) NULL,
+ `oh_new` varchar(255) NULL,
+ `oh_by` varchar(255) NOT NULL,
+ `oh_from` varchar(255) NOT NULL,
+ `oh_on` datetime NOT NULL,
+ INDEX `object_history_idx_oh_type_oh_name` (`oh_type`, `oh_name`),
+ PRIMARY KEY (`oh_id`)
+);
+
+DROP TABLE IF EXISTS `sync_targets`;
+
+--
+-- Table: `sync_targets`
+--
+CREATE TABLE `sync_targets` (
+ `st_name` varchar(255) NOT NULL,
+ PRIMARY KEY (`st_name`)
+);
+
+DROP TABLE IF EXISTS `types`;
+
+--
+-- Table: `types`
+--
+CREATE TABLE `types` (
+ `ty_name` varchar(16) NOT NULL,
+ `ty_class` varchar(64) NULL,
+ PRIMARY KEY (`ty_name`)
+) ENGINE=InnoDB;
+
+DROP TABLE IF EXISTS `acl_entries`;
+
+--
+-- Table: `acl_entries`
+--
+CREATE TABLE `acl_entries` (
+ `ae_id` integer NOT NULL,
+ `ae_scheme` varchar(32) NOT NULL,
+ `ae_identifier` varchar(255) NOT NULL,
+ INDEX `acl_entries_idx_ae_scheme` (`ae_scheme`),
+ INDEX `acl_entries_idx_ae_id` (`ae_id`),
+ PRIMARY KEY (`ae_id`, `ae_scheme`, `ae_identifier`),
+ CONSTRAINT `acl_entries_fk_ae_scheme` FOREIGN KEY (`ae_scheme`) REFERENCES `acl_schemes` (`as_name`),
+ CONSTRAINT `acl_entries_fk_ae_id` FOREIGN KEY (`ae_id`) REFERENCES `acls` (`ac_id`) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB;
+
+DROP TABLE IF EXISTS `objects`;
+
+--
+-- Table: `objects`
+--
+CREATE TABLE `objects` (
+ `ob_type` varchar(16) NOT NULL,
+ `ob_name` varchar(255) NOT NULL,
+ `ob_owner` integer NULL,
+ `ob_acl_get` integer NULL,
+ `ob_acl_store` integer NULL,
+ `ob_acl_show` integer NULL,
+ `ob_acl_destroy` integer NULL,
+ `ob_acl_flags` integer NULL,
+ `ob_expires` datetime NULL,
+ `ob_created_by` varchar(255) NOT NULL,
+ `ob_created_from` varchar(255) NOT NULL,
+ `ob_created_on` datetime NOT NULL,
+ `ob_stored_by` varchar(255) NULL,
+ `ob_stored_from` varchar(255) NULL,
+ `ob_stored_on` datetime NULL,
+ `ob_downloaded_by` varchar(255) NULL,
+ `ob_downloaded_from` varchar(255) NULL,
+ `ob_downloaded_on` datetime NULL,
+ `ob_comment` varchar(255) NULL,
+ INDEX `objects_idx_ob_acl_destroy` (`ob_acl_destroy`),
+ INDEX `objects_idx_ob_acl_flags` (`ob_acl_flags`),
+ INDEX `objects_idx_ob_acl_get` (`ob_acl_get`),
+ INDEX `objects_idx_ob_owner` (`ob_owner`),
+ INDEX `objects_idx_ob_acl_show` (`ob_acl_show`),
+ INDEX `objects_idx_ob_acl_store` (`ob_acl_store`),
+ INDEX `objects_idx_ob_type` (`ob_type`),
+ PRIMARY KEY (`ob_name`, `ob_type`),
+ CONSTRAINT `objects_fk_ob_acl_destroy` FOREIGN KEY (`ob_acl_destroy`) REFERENCES `acls` (`ac_id`) ON DELETE CASCADE ON UPDATE CASCADE,
+ CONSTRAINT `objects_fk_ob_acl_flags` FOREIGN KEY (`ob_acl_flags`) REFERENCES `acls` (`ac_id`) ON DELETE CASCADE ON UPDATE CASCADE,
+ CONSTRAINT `objects_fk_ob_acl_get` FOREIGN KEY (`ob_acl_get`) REFERENCES `acls` (`ac_id`) ON DELETE CASCADE ON UPDATE CASCADE,
+ CONSTRAINT `objects_fk_ob_owner` FOREIGN KEY (`ob_owner`) REFERENCES `acls` (`ac_id`) ON DELETE CASCADE ON UPDATE CASCADE,
+ CONSTRAINT `objects_fk_ob_acl_show` FOREIGN KEY (`ob_acl_show`) REFERENCES `acls` (`ac_id`) ON DELETE CASCADE ON UPDATE CASCADE,
+ CONSTRAINT `objects_fk_ob_acl_store` FOREIGN KEY (`ob_acl_store`) REFERENCES `acls` (`ac_id`) ON DELETE CASCADE ON UPDATE CASCADE,
+ CONSTRAINT `objects_fk_ob_type` FOREIGN KEY (`ob_type`) REFERENCES `types` (`ty_name`)
+) ENGINE=InnoDB;
+
+DROP TABLE IF EXISTS `duo`;
+
+--
+-- Table: `duo`
+--
+CREATE TABLE `duo` (
+ `du_name` varchar(255) NOT NULL,
+ `du_type` varchar(16) NOT NULL,
+ `du_key` varchar(255) NOT NULL,
+ INDEX `duo_idx_du_type_du_name` (`du_type`, `du_name`),
+ PRIMARY KEY (`du_name`, `du_type`),
+ CONSTRAINT `duo_fk_du_type_du_name` FOREIGN KEY (`du_type`, `du_name`) REFERENCES `objects` (`ob_type`, `ob_name`)
+) ENGINE=InnoDB;
+
+SET foreign_key_checks=1;
+
diff --git a/perl/sql/Wallet-Schema-0.10-PostgreSQL.sql b/perl/sql/Wallet-Schema-0.10-PostgreSQL.sql
new file mode 100644
index 0000000..3bcb0ae
--- /dev/null
+++ b/perl/sql/Wallet-Schema-0.10-PostgreSQL.sql
@@ -0,0 +1,216 @@
+--
+-- Created by SQL::Translator::Producer::PostgreSQL
+-- Created on Thu Oct 9 20:54:56 2014
+--
+--
+-- Table: acl_history.
+--
+DROP TABLE "acl_history" CASCADE;
+CREATE TABLE "acl_history" (
+ "ah_id" serial NOT NULL,
+ "ah_acl" integer NOT NULL,
+ "ah_name" character varying(255),
+ "ah_action" character varying(16) NOT NULL,
+ "ah_scheme" character varying(32),
+ "ah_identifier" character varying(255),
+ "ah_by" character varying(255) NOT NULL,
+ "ah_from" character varying(255) NOT NULL,
+ "ah_on" timestamp NOT NULL,
+ PRIMARY KEY ("ah_id")
+);
+CREATE INDEX "acl_history_idx_ah_acl" on "acl_history" ("ah_acl");
+CREATE INDEX "acl_history_idx_ah_name" on "acl_history" ("ah_name");
+
+--
+-- Table: acl_schemes.
+--
+DROP TABLE "acl_schemes" CASCADE;
+CREATE TABLE "acl_schemes" (
+ "as_name" character varying(32) NOT NULL,
+ "as_class" character varying(64),
+ PRIMARY KEY ("as_name")
+);
+
+--
+-- Table: acls.
+--
+DROP TABLE "acls" CASCADE;
+CREATE TABLE "acls" (
+ "ac_id" serial NOT NULL,
+ "ac_name" character varying(255) NOT NULL,
+ PRIMARY KEY ("ac_id"),
+ CONSTRAINT "ac_name" UNIQUE ("ac_name")
+);
+
+--
+-- Table: enctypes.
+--
+DROP TABLE "enctypes" CASCADE;
+CREATE TABLE "enctypes" (
+ "en_name" character varying(255) NOT NULL,
+ PRIMARY KEY ("en_name")
+);
+
+--
+-- Table: flags.
+--
+DROP TABLE "flags" CASCADE;
+CREATE TABLE "flags" (
+ "fl_type" character varying(16) NOT NULL,
+ "fl_name" character varying(255) NOT NULL,
+ "fl_flag" character varying NOT NULL,
+ PRIMARY KEY ("fl_type", "fl_name", "fl_flag")
+);
+
+--
+-- Table: keytab_enctypes.
+--
+DROP TABLE "keytab_enctypes" CASCADE;
+CREATE TABLE "keytab_enctypes" (
+ "ke_name" character varying(255) NOT NULL,
+ "ke_enctype" character varying(255) NOT NULL,
+ PRIMARY KEY ("ke_name", "ke_enctype")
+);
+
+--
+-- Table: keytab_sync.
+--
+DROP TABLE "keytab_sync" CASCADE;
+CREATE TABLE "keytab_sync" (
+ "ks_name" character varying(255) NOT NULL,
+ "ks_target" character varying(255) NOT NULL,
+ PRIMARY KEY ("ks_name", "ks_target")
+);
+
+--
+-- Table: object_history.
+--
+DROP TABLE "object_history" CASCADE;
+CREATE TABLE "object_history" (
+ "oh_id" serial NOT NULL,
+ "oh_type" character varying(16) NOT NULL,
+ "oh_name" character varying(255) NOT NULL,
+ "oh_action" character varying(16) NOT NULL,
+ "oh_field" character varying(16),
+ "oh_type_field" character varying(255),
+ "oh_old" character varying(255),
+ "oh_new" character varying(255),
+ "oh_by" character varying(255) NOT NULL,
+ "oh_from" character varying(255) NOT NULL,
+ "oh_on" timestamp NOT NULL,
+ PRIMARY KEY ("oh_id")
+);
+CREATE INDEX "object_history_idx_oh_type_oh_name" on "object_history" ("oh_type", "oh_name");
+
+--
+-- Table: sync_targets.
+--
+DROP TABLE "sync_targets" CASCADE;
+CREATE TABLE "sync_targets" (
+ "st_name" character varying(255) NOT NULL,
+ PRIMARY KEY ("st_name")
+);
+
+--
+-- Table: types.
+--
+DROP TABLE "types" CASCADE;
+CREATE TABLE "types" (
+ "ty_name" character varying(16) NOT NULL,
+ "ty_class" character varying(64),
+ PRIMARY KEY ("ty_name")
+);
+
+--
+-- Table: acl_entries.
+--
+DROP TABLE "acl_entries" CASCADE;
+CREATE TABLE "acl_entries" (
+ "ae_id" integer NOT NULL,
+ "ae_scheme" character varying(32) NOT NULL,
+ "ae_identifier" character varying(255) NOT NULL,
+ PRIMARY KEY ("ae_id", "ae_scheme", "ae_identifier")
+);
+CREATE INDEX "acl_entries_idx_ae_scheme" on "acl_entries" ("ae_scheme");
+CREATE INDEX "acl_entries_idx_ae_id" on "acl_entries" ("ae_id");
+
+--
+-- Table: objects.
+--
+DROP TABLE "objects" CASCADE;
+CREATE TABLE "objects" (
+ "ob_type" character varying(16) NOT NULL,
+ "ob_name" character varying(255) NOT NULL,
+ "ob_owner" integer,
+ "ob_acl_get" integer,
+ "ob_acl_store" integer,
+ "ob_acl_show" integer,
+ "ob_acl_destroy" integer,
+ "ob_acl_flags" integer,
+ "ob_expires" timestamp,
+ "ob_created_by" character varying(255) NOT NULL,
+ "ob_created_from" character varying(255) NOT NULL,
+ "ob_created_on" timestamp NOT NULL,
+ "ob_stored_by" character varying(255),
+ "ob_stored_from" character varying(255),
+ "ob_stored_on" timestamp,
+ "ob_downloaded_by" character varying(255),
+ "ob_downloaded_from" character varying(255),
+ "ob_downloaded_on" timestamp,
+ "ob_comment" character varying(255),
+ PRIMARY KEY ("ob_name", "ob_type")
+);
+CREATE INDEX "objects_idx_ob_acl_destroy" on "objects" ("ob_acl_destroy");
+CREATE INDEX "objects_idx_ob_acl_flags" on "objects" ("ob_acl_flags");
+CREATE INDEX "objects_idx_ob_acl_get" on "objects" ("ob_acl_get");
+CREATE INDEX "objects_idx_ob_owner" on "objects" ("ob_owner");
+CREATE INDEX "objects_idx_ob_acl_show" on "objects" ("ob_acl_show");
+CREATE INDEX "objects_idx_ob_acl_store" on "objects" ("ob_acl_store");
+CREATE INDEX "objects_idx_ob_type" on "objects" ("ob_type");
+
+--
+-- Table: duo.
+--
+DROP TABLE "duo" CASCADE;
+CREATE TABLE "duo" (
+ "du_name" character varying(255) NOT NULL,
+ "du_type" character varying(16) NOT NULL,
+ "du_key" character varying(255) NOT NULL,
+ PRIMARY KEY ("du_name", "du_type")
+);
+CREATE INDEX "duo_idx_du_type_du_name" on "duo" ("du_type", "du_name");
+
+--
+-- Foreign Key Definitions
+--
+
+ALTER TABLE "acl_entries" ADD CONSTRAINT "acl_entries_fk_ae_scheme" FOREIGN KEY ("ae_scheme")
+ REFERENCES "acl_schemes" ("as_name") DEFERRABLE;
+
+ALTER TABLE "acl_entries" ADD CONSTRAINT "acl_entries_fk_ae_id" FOREIGN KEY ("ae_id")
+ REFERENCES "acls" ("ac_id") ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE;
+
+ALTER TABLE "objects" ADD CONSTRAINT "objects_fk_ob_acl_destroy" FOREIGN KEY ("ob_acl_destroy")
+ REFERENCES "acls" ("ac_id") ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE;
+
+ALTER TABLE "objects" ADD CONSTRAINT "objects_fk_ob_acl_flags" FOREIGN KEY ("ob_acl_flags")
+ REFERENCES "acls" ("ac_id") ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE;
+
+ALTER TABLE "objects" ADD CONSTRAINT "objects_fk_ob_acl_get" FOREIGN KEY ("ob_acl_get")
+ REFERENCES "acls" ("ac_id") ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE;
+
+ALTER TABLE "objects" ADD CONSTRAINT "objects_fk_ob_owner" FOREIGN KEY ("ob_owner")
+ REFERENCES "acls" ("ac_id") ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE;
+
+ALTER TABLE "objects" ADD CONSTRAINT "objects_fk_ob_acl_show" FOREIGN KEY ("ob_acl_show")
+ REFERENCES "acls" ("ac_id") ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE;
+
+ALTER TABLE "objects" ADD CONSTRAINT "objects_fk_ob_acl_store" FOREIGN KEY ("ob_acl_store")
+ REFERENCES "acls" ("ac_id") ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE;
+
+ALTER TABLE "objects" ADD CONSTRAINT "objects_fk_ob_type" FOREIGN KEY ("ob_type")
+ REFERENCES "types" ("ty_name") DEFERRABLE;
+
+ALTER TABLE "duo" ADD CONSTRAINT "duo_fk_du_type_du_name" FOREIGN KEY ("du_type", "du_name")
+ REFERENCES "objects" ("ob_type", "ob_name") DEFERRABLE;
+
diff --git a/perl/sql/Wallet-Schema-0.10-SQLite.sql b/perl/sql/Wallet-Schema-0.10-SQLite.sql
new file mode 100644
index 0000000..94a185c
--- /dev/null
+++ b/perl/sql/Wallet-Schema-0.10-SQLite.sql
@@ -0,0 +1,220 @@
+--
+-- Created by SQL::Translator::Producer::SQLite
+-- Created on Thu Oct 9 20:51:25 2014
+--
+
+BEGIN TRANSACTION;
+
+--
+-- Table: acl_history
+--
+DROP TABLE IF EXISTS acl_history;
+
+CREATE TABLE acl_history (
+ ah_id INTEGER PRIMARY KEY NOT NULL,
+ ah_acl integer NOT NULL,
+ ah_name varchar(255),
+ ah_action varchar(16) NOT NULL,
+ ah_scheme varchar(32),
+ ah_identifier varchar(255),
+ ah_by varchar(255) NOT NULL,
+ ah_from varchar(255) NOT NULL,
+ ah_on datetime NOT NULL
+);
+
+CREATE INDEX acl_history_idx_ah_acl ON acl_history (ah_acl);
+
+CREATE INDEX acl_history_idx_ah_name ON acl_history (ah_name);
+
+--
+-- Table: acl_schemes
+--
+DROP TABLE IF EXISTS acl_schemes;
+
+CREATE TABLE acl_schemes (
+ as_name varchar(32) NOT NULL,
+ as_class varchar(64),
+ PRIMARY KEY (as_name)
+);
+
+--
+-- Table: acls
+--
+DROP TABLE IF EXISTS acls;
+
+CREATE TABLE acls (
+ ac_id INTEGER PRIMARY KEY NOT NULL,
+ ac_name varchar(255) NOT NULL
+);
+
+CREATE UNIQUE INDEX ac_name ON acls (ac_name);
+
+--
+-- Table: enctypes
+--
+DROP TABLE IF EXISTS enctypes;
+
+CREATE TABLE enctypes (
+ en_name varchar(255) NOT NULL,
+ PRIMARY KEY (en_name)
+);
+
+--
+-- Table: flags
+--
+DROP TABLE IF EXISTS flags;
+
+CREATE TABLE flags (
+ fl_type varchar(16) NOT NULL,
+ fl_name varchar(255) NOT NULL,
+ fl_flag enum NOT NULL,
+ PRIMARY KEY (fl_type, fl_name, fl_flag)
+);
+
+--
+-- Table: keytab_enctypes
+--
+DROP TABLE IF EXISTS keytab_enctypes;
+
+CREATE TABLE keytab_enctypes (
+ ke_name varchar(255) NOT NULL,
+ ke_enctype varchar(255) NOT NULL,
+ PRIMARY KEY (ke_name, ke_enctype)
+);
+
+--
+-- Table: keytab_sync
+--
+DROP TABLE IF EXISTS keytab_sync;
+
+CREATE TABLE keytab_sync (
+ ks_name varchar(255) NOT NULL,
+ ks_target varchar(255) NOT NULL,
+ PRIMARY KEY (ks_name, ks_target)
+);
+
+--
+-- Table: object_history
+--
+DROP TABLE IF EXISTS object_history;
+
+CREATE TABLE object_history (
+ oh_id INTEGER PRIMARY KEY NOT NULL,
+ oh_type varchar(16) NOT NULL,
+ oh_name varchar(255) NOT NULL,
+ oh_action varchar(16) NOT NULL,
+ oh_field varchar(16),
+ oh_type_field varchar(255),
+ oh_old varchar(255),
+ oh_new varchar(255),
+ oh_by varchar(255) NOT NULL,
+ oh_from varchar(255) NOT NULL,
+ oh_on datetime NOT NULL
+);
+
+CREATE INDEX object_history_idx_oh_type_oh_name ON object_history (oh_type, oh_name);
+
+--
+-- Table: sync_targets
+--
+DROP TABLE IF EXISTS sync_targets;
+
+CREATE TABLE sync_targets (
+ st_name varchar(255) NOT NULL,
+ PRIMARY KEY (st_name)
+);
+
+--
+-- Table: types
+--
+DROP TABLE IF EXISTS types;
+
+CREATE TABLE types (
+ ty_name varchar(16) NOT NULL,
+ ty_class varchar(64),
+ PRIMARY KEY (ty_name)
+);
+
+--
+-- Table: acl_entries
+--
+DROP TABLE IF EXISTS acl_entries;
+
+CREATE TABLE acl_entries (
+ ae_id integer NOT NULL,
+ ae_scheme varchar(32) NOT NULL,
+ ae_identifier varchar(255) NOT NULL,
+ PRIMARY KEY (ae_id, ae_scheme, ae_identifier),
+ FOREIGN KEY (ae_scheme) REFERENCES acl_schemes(as_name),
+ FOREIGN KEY (ae_id) REFERENCES acls(ac_id) ON DELETE CASCADE ON UPDATE CASCADE
+);
+
+CREATE INDEX acl_entries_idx_ae_scheme ON acl_entries (ae_scheme);
+
+CREATE INDEX acl_entries_idx_ae_id ON acl_entries (ae_id);
+
+--
+-- Table: objects
+--
+DROP TABLE IF EXISTS objects;
+
+CREATE TABLE objects (
+ ob_type varchar(16) NOT NULL,
+ ob_name varchar(255) NOT NULL,
+ ob_owner integer,
+ ob_acl_get integer,
+ ob_acl_store integer,
+ ob_acl_show integer,
+ ob_acl_destroy integer,
+ ob_acl_flags integer,
+ ob_expires datetime,
+ ob_created_by varchar(255) NOT NULL,
+ ob_created_from varchar(255) NOT NULL,
+ ob_created_on datetime NOT NULL,
+ ob_stored_by varchar(255),
+ ob_stored_from varchar(255),
+ ob_stored_on datetime,
+ ob_downloaded_by varchar(255),
+ ob_downloaded_from varchar(255),
+ ob_downloaded_on datetime,
+ ob_comment varchar(255),
+ PRIMARY KEY (ob_name, ob_type),
+ FOREIGN KEY (ob_acl_destroy) REFERENCES acls(ac_id) ON DELETE CASCADE ON UPDATE CASCADE,
+ FOREIGN KEY (ob_acl_flags) REFERENCES acls(ac_id) ON DELETE CASCADE ON UPDATE CASCADE,
+ FOREIGN KEY (ob_acl_get) REFERENCES acls(ac_id) ON DELETE CASCADE ON UPDATE CASCADE,
+ FOREIGN KEY (ob_owner) REFERENCES acls(ac_id) ON DELETE CASCADE ON UPDATE CASCADE,
+ FOREIGN KEY (ob_acl_show) REFERENCES acls(ac_id) ON DELETE CASCADE ON UPDATE CASCADE,
+ FOREIGN KEY (ob_acl_store) REFERENCES acls(ac_id) ON DELETE CASCADE ON UPDATE CASCADE,
+ FOREIGN KEY (ob_type) REFERENCES types(ty_name)
+);
+
+CREATE INDEX objects_idx_ob_acl_destroy ON objects (ob_acl_destroy);
+
+CREATE INDEX objects_idx_ob_acl_flags ON objects (ob_acl_flags);
+
+CREATE INDEX objects_idx_ob_acl_get ON objects (ob_acl_get);
+
+CREATE INDEX objects_idx_ob_owner ON objects (ob_owner);
+
+CREATE INDEX objects_idx_ob_acl_show ON objects (ob_acl_show);
+
+CREATE INDEX objects_idx_ob_acl_store ON objects (ob_acl_store);
+
+CREATE INDEX objects_idx_ob_type ON objects (ob_type);
+
+--
+-- Table: duo
+--
+DROP TABLE IF EXISTS duo;
+
+CREATE TABLE duo (
+ du_name varchar(255) NOT NULL,
+ du_type varchar(16) NOT NULL,
+ du_key varchar(255) NOT NULL,
+ PRIMARY KEY (du_name, du_type),
+ FOREIGN KEY (du_type, du_name) REFERENCES objects(ob_type, ob_name)
+);
+
+CREATE INDEX duo_idx_du_type_du_name ON duo (du_type, du_name);
+
+COMMIT;
diff --git a/perl/t/data/duo/integration-ldap.json b/perl/t/data/duo/integration-ldap.json
new file mode 100644
index 0000000..78a4c9f
--- /dev/null
+++ b/perl/t/data/duo/integration-ldap.json
@@ -0,0 +1,11 @@
+{
+ "enroll_policy": "enroll",
+ "greeting": "",
+ "groups_allowed": [],
+ "integration_key": "DIRWIH0ZZPV4G88B37VQ",
+ "name": "Integration for LDAP proxy",
+ "notes": "",
+ "secret_key": "QO4ZLqQVRIOZYkHfdPDORfcNf8LeXIbCWwHazY7o",
+ "type": "ldap",
+ "visual_style": "default"
+}
diff --git a/perl/t/data/duo/integration-radius.json b/perl/t/data/duo/integration-radius.json
new file mode 100644
index 0000000..514a33e
--- /dev/null
+++ b/perl/t/data/duo/integration-radius.json
@@ -0,0 +1,11 @@
+{
+ "enroll_policy": "enroll",
+ "greeting": "",
+ "groups_allowed": [],
+ "integration_key": "DIRWIH0ZZPV4G88B37VQ",
+ "name": "Integration for Radius proxy",
+ "notes": "",
+ "secret_key": "QO4ZLqQVRIOZYkHfdPDORfcNf8LeXIbCWwHazY7o",
+ "type": "radius",
+ "visual_style": "default"
+}
diff --git a/perl/t/data/duo/integration-rdp.json b/perl/t/data/duo/integration-rdp.json
new file mode 100644
index 0000000..28e925f
--- /dev/null
+++ b/perl/t/data/duo/integration-rdp.json
@@ -0,0 +1,11 @@
+{
+ "enroll_policy": "enroll",
+ "greeting": "",
+ "groups_allowed": [],
+ "integration_key": "DIRWIH0ZZPV4G88B37VQ",
+ "name": "Integration for Radius proxy",
+ "notes": "",
+ "secret_key": "QO4ZLqQVRIOZYkHfdPDORfcNf8LeXIbCWwHazY7o",
+ "type": "rdp",
+ "visual_style": "default"
+}
diff --git a/perl/t/general/admin.t b/perl/t/general/admin.t
index 47396c6..17671b6 100755
--- a/perl/t/general/admin.t
+++ b/perl/t/general/admin.t
@@ -69,7 +69,7 @@ is ($admin->reinitialize ('admin@EXAMPLE.COM'), 1,
SKIP: {
my @path = (split (':', $ENV{PATH}));
my ($sqlite) = grep { -x $_ } map { "$_/sqlite3" } @path;
- skip 'sqlite3 not found', 5 unless $sqlite;
+ skip 'sqlite3 not found', 7 unless $sqlite;
# Delete all tables and then redump them straight from the SQL file to
# avoid getting the version table.
diff --git a/perl/t/object/duo-ldap.t b/perl/t/object/duo-ldap.t
new file mode 100644
index 0000000..3648eba
--- /dev/null
+++ b/perl/t/object/duo-ldap.t
@@ -0,0 +1,160 @@
+#!/usr/bin/perl
+#
+# Tests for the Duo Auth proxy LDAP integration object implementation.
+#
+# Written by Russ Allbery <eagle@eyrie.org>
+# Copyright 2014
+# The Board of Trustees of the Leland Stanford Junior University
+#
+# See LICENSE for licensing terms.
+
+use strict;
+use warnings;
+
+use POSIX qw(strftime);
+use Test::More;
+
+BEGIN {
+ eval 'use Net::Duo';
+ plan skip_all => 'Net::Duo required for testing duo'
+ if $@;
+ eval 'use Net::Duo::Mock::Agent';
+ plan skip_all => 'Net::Duo::Mock::Agent required for testing duo'
+ if $@;
+}
+
+BEGIN {
+ use_ok('Wallet::Admin');
+ use_ok('Wallet::Config');
+ use_ok('Wallet::Object::Duo::LDAPProxy');
+}
+
+use lib 't/lib';
+use Util;
+
+# Some global defaults to use.
+my $user = 'admin@EXAMPLE.COM';
+my $host = 'localhost';
+my @trace = ($user, $host, time);
+my $date = strftime ('%Y-%m-%d %H:%M:%S', localtime $trace[2]);
+
+# Flush all output immediately.
+$| = 1;
+
+# Use Wallet::Admin to set up the database.
+db_setup;
+my $admin = eval { Wallet::Admin->new };
+is ($@, '', 'Database connection succeeded');
+is ($admin->reinitialize ($user), 1, 'Database initialization succeeded');
+my $schema = $admin->schema;
+
+# Create a mock object to use for Duo calls.
+my $mock = Net::Duo::Mock::Agent->new ({ key_file => 't/data/duo/keys.json' });
+
+# Test error handling in the absence of configuration.
+my $object = eval {
+ Wallet::Object::Duo::LDAPProxy->new ('duo-ldap', 'test', $schema);
+};
+is ($object, undef, 'Wallet::Object::Duo::LDAPProxy new with no config failed');
+is ($@, "duo object implementation not configured\n", '...with correct error');
+$object = eval {
+ Wallet::Object::Duo::LDAPProxy->create ('duo-ldap', 'test', $schema,
+ @trace);
+};
+is ($object, undef, 'Wallet::Object::Duo::LDAPProxy creation with no config failed');
+is ($@, "duo object implementation not configured\n", '...with correct error');
+
+# Set up the Duo configuration.
+$Wallet::Config::DUO_AGENT = $mock;
+$Wallet::Config::DUO_KEY_FILE = 't/data/duo/keys.json';
+
+# Test creating an integration.
+note ('Test creating an integration');
+my $expected = {
+ name => 'test (ldapproxy)',
+ notes => 'Managed by wallet',
+ type => 'ldapproxy',
+};
+$mock->expect (
+ {
+ method => 'POST',
+ uri => '/admin/v1/integrations',
+ content => $expected,
+ response_file => 't/data/duo/integration.json',
+ }
+);
+$object = Wallet::Object::Duo::LDAPProxy->create ('duo-ldap', 'test', $schema,
+ @trace);
+isa_ok ($object, 'Wallet::Object::Duo::LDAPProxy');
+
+# Check the metadata about the new wallet object.
+$expected = <<"EOO";
+ Type: duo-ldap
+ Name: test
+ Duo key: DIRWIH0ZZPV4G88B37VQ
+ Created by: $user
+ Created from: $host
+ Created on: $date
+EOO
+is ($object->show, $expected, 'Show output is correct');
+
+# Test retrieving the integration information.
+note ('Test retrieving an integration');
+$mock->expect (
+ {
+ method => 'GET',
+ uri => '/admin/v1/integrations/DIRWIH0ZZPV4G88B37VQ',
+ response_file => 't/data/duo/integration-ldap.json',
+ }
+);
+my $data = $object->get (@trace);
+ok (defined ($data), 'Retrieval succeeds');
+$expected = <<'EOO';
+[ldap_server_challenge]
+ikey = DIRWIH0ZZPV4G88B37VQ
+skey = QO4ZLqQVRIOZYkHfdPDORfcNf8LeXIbCWwHazY7o
+api_host = example-admin.duosecurity.com
+EOO
+is ($data, $expected, '...and integration data is correct');
+
+# Ensure that we can't retrieve the object when locked.
+is ($object->flag_set ('locked', @trace), 1,
+ 'Setting object to locked succeeds');
+is ($object->get, undef, '...and now get fails');
+is ($object->error, 'cannot get duo-ldap:test: object is locked',
+ '...with correct error');
+is ($object->flag_clear ('locked', @trace), 1,
+ '...and clearing locked flag works');
+
+# Create a new object by wallet type and name.
+$object = Wallet::Object::Duo::LDAPProxy->new ('duo-ldap', 'test', $schema);
+
+# Test deleting an integration. We can't test this entirely properly because
+# currently Net::Duo::Mock::Agent doesn't support stacking multiple expected
+# calls and delete makes two calls.
+note ('Test deleting an integration');
+$mock->expect (
+ {
+ method => 'GET',
+ uri => '/admin/v1/integrations/DIRWIH0ZZPV4G88B37VQ',
+ response_file => 't/data/duo/integration.json',
+ }
+);
+TODO: {
+ local $TODO = 'Net::Duo::Mock::Agent not yet capable';
+
+ is ($object->destroy (@trace), 1, 'Duo object deletion succeeded');
+ $object = eval { Wallet::Object::Duo::LDAPProxy->new ('duo-ldap', 'test',
+ $schema) };
+ is ($object, undef, '...and now object cannot be retrieved');
+ is ($@, "cannot find duo:test\n", '...with correct error');
+}
+
+# Clean up.
+$admin->destroy;
+END {
+ unlink ('wallet-db');
+}
+
+# Done testing.
+done_testing ();
diff --git a/perl/t/object/duo-pam.t b/perl/t/object/duo-pam.t
new file mode 100644
index 0000000..7b88787
--- /dev/null
+++ b/perl/t/object/duo-pam.t
@@ -0,0 +1,159 @@
+#!/usr/bin/perl
+#
+# Tests for the Duo PAM integration object implementation.
+#
+# Written by Russ Allbery <eagle@eyrie.org>
+# Copyright 2014
+# The Board of Trustees of the Leland Stanford Junior University
+#
+# See LICENSE for licensing terms.
+
+use strict;
+use warnings;
+
+use POSIX qw(strftime);
+use Test::More;
+
+BEGIN {
+ eval 'use Net::Duo';
+ plan skip_all => 'Net::Duo required for testing duo'
+ if $@;
+ eval 'use Net::Duo::Mock::Agent';
+ plan skip_all => 'Net::Duo::Mock::Agent required for testing duo'
+ if $@;
+}
+
+BEGIN {
+ use_ok('Wallet::Admin');
+ use_ok('Wallet::Config');
+ use_ok('Wallet::Object::Duo::PAM');
+}
+
+use lib 't/lib';
+use Util;
+
+# Some global defaults to use.
+my $user = 'admin@EXAMPLE.COM';
+my $host = 'localhost';
+my @trace = ($user, $host, time);
+my $date = strftime ('%Y-%m-%d %H:%M:%S', localtime $trace[2]);
+
+# Flush all output immediately.
+$| = 1;
+
+# Use Wallet::Admin to set up the database.
+db_setup;
+my $admin = eval { Wallet::Admin->new };
+is ($@, '', 'Database connection succeeded');
+is ($admin->reinitialize ($user), 1, 'Database initialization succeeded');
+my $schema = $admin->schema;
+
+# Create a mock object to use for Duo calls.
+my $mock = Net::Duo::Mock::Agent->new ({ key_file => 't/data/duo/keys.json' });
+
+# Test error handling in the absence of configuration.
+my $object = eval {
+ Wallet::Object::Duo::PAM->new ('duo-pam', 'test', $schema);
+};
+is ($object, undef, 'Wallet::Object::Duo::PAM new with no config failed');
+is ($@, "duo object implementation not configured\n", '...with correct error');
+$object = eval {
+ Wallet::Object::Duo::PAM->create ('duo-pam', 'test', $schema, @trace);
+};
+is ($object, undef, 'Wallet::Object::Duo::PAM creation with no config failed');
+is ($@, "duo object implementation not configured\n", '...with correct error');
+
+# Set up the Duo configuration.
+$Wallet::Config::DUO_AGENT = $mock;
+$Wallet::Config::DUO_KEY_FILE = 't/data/duo/keys.json';
+
+# Test creating an integration.
+note ('Test creating an integration');
+my $expected = {
+ name => 'test (unix)',
+ notes => 'Managed by wallet',
+ type => 'unix',
+};
+$mock->expect (
+ {
+ method => 'POST',
+ uri => '/admin/v1/integrations',
+ content => $expected,
+ response_file => 't/data/duo/integration.json',
+ }
+);
+$object = Wallet::Object::Duo::PAM->create ('duo-pam', 'test', $schema,
+ @trace);
+isa_ok ($object, 'Wallet::Object::Duo::PAM');
+
+# Check the metadata about the new wallet object.
+$expected = <<"EOO";
+ Type: duo-pam
+ Name: test
+ Duo key: DIRWIH0ZZPV4G88B37VQ
+ Created by: $user
+ Created from: $host
+ Created on: $date
+EOO
+is ($object->show, $expected, 'Show output is correct');
+
+# Test retrieving the integration information.
+note ('Test retrieving an integration');
+$mock->expect (
+ {
+ method => 'GET',
+ uri => '/admin/v1/integrations/DIRWIH0ZZPV4G88B37VQ',
+ response_file => 't/data/duo/integration.json',
+ }
+);
+my $data = $object->get (@trace);
+ok (defined ($data), 'Retrieval succeeds');
+$expected = <<'EOO';
+[duo]
+ikey = DIRWIH0ZZPV4G88B37VQ
+skey = QO4ZLqQVRIOZYkHfdPDORfcNf8LeXIbCWwHazY7o
+host = example-admin.duosecurity.com
+EOO
+is ($data, $expected, '...and integration data is correct');
+
+# Ensure that we can't retrieve the object when locked.
+is ($object->flag_set ('locked', @trace), 1,
+ 'Setting object to locked succeeds');
+is ($object->get, undef, '...and now get fails');
+is ($object->error, 'cannot get duo-pam:test: object is locked',
+ '...with correct error');
+is ($object->flag_clear ('locked', @trace), 1,
+ '...and clearing locked flag works');
+
+# Create a new object by wallet type and name.
+$object = Wallet::Object::Duo::PAM->new ('duo-pam', 'test', $schema);
+
+# Test deleting an integration. We can't test this entirely properly because
+# currently Net::Duo::Mock::Agent doesn't support stacking multiple expected
+# calls and delete makes two calls.
+note ('Test deleting an integration');
+$mock->expect (
+ {
+ method => 'GET',
+ uri => '/admin/v1/integrations/DIRWIH0ZZPV4G88B37VQ',
+ response_file => 't/data/duo/integration.json',
+ }
+);
+TODO: {
+ local $TODO = 'Net::Duo::Mock::Agent not yet capable';
+
+ is ($object->destroy (@trace), 1, 'Duo object deletion succeeded');
+ $object = eval { Wallet::Object::Duo::PAM->new ('duo-pam', 'test',
+ $schema) };
+ is ($object, undef, '...and now object cannot be retrieved');
+ is ($@, "cannot find duo:test\n", '...with correct error');
+}
+
+# Clean up.
+$admin->destroy;
+END {
+ unlink ('wallet-db');
+}
+
+# Done testing.
+done_testing ();
diff --git a/perl/t/object/duo-radius.t b/perl/t/object/duo-radius.t
new file mode 100644
index 0000000..f258518
--- /dev/null
+++ b/perl/t/object/duo-radius.t
@@ -0,0 +1,165 @@
+#!/usr/bin/perl
+#
+# Tests for the Duo Auth proxy Radius integration object implementation.
+#
+# Written by Russ Allbery <eagle@eyrie.org>
+# Copyright 2014
+# The Board of Trustees of the Leland Stanford Junior University
+#
+# See LICENSE for licensing terms.
+
+use strict;
+use warnings;
+
+use POSIX qw(strftime);
+use Test::More;
+
+BEGIN {
+ eval 'use Net::Duo';
+ plan skip_all => 'Net::Duo required for testing duo'
+ if $@;
+ eval 'use Net::Duo::Mock::Agent';
+ plan skip_all => 'Net::Duo::Mock::Agent required for testing duo'
+ if $@;
+}
+
+BEGIN {
+ use_ok('Wallet::Admin');
+ use_ok('Wallet::Config');
+ use_ok('Wallet::Object::Duo::RadiusProxy');
+}
+
+use lib 't/lib';
+use Util;
+
+# Some global defaults to use.
+my $user = 'admin@EXAMPLE.COM';
+my $host = 'localhost';
+my @trace = ($user, $host, time);
+my $date = strftime ('%Y-%m-%d %H:%M:%S', localtime $trace[2]);
+
+# Flush all output immediately.
+$| = 1;
+
+# Use Wallet::Admin to set up the database.
+db_setup;
+my $admin = eval { Wallet::Admin->new };
+is ($@, '', 'Database connection succeeded');
+is ($admin->reinitialize ($user), 1, 'Database initialization succeeded');
+my $schema = $admin->schema;
+
+# Create a mock object to use for Duo calls.
+my $mock = Net::Duo::Mock::Agent->new ({ key_file => 't/data/duo/keys.json' });
+
+# Test error handling in the absence of configuration.
+my $object = eval {
+ Wallet::Object::Duo::RadiusProxy->new ('duo-raduys', 'test', $schema);
+};
+is ($object, undef,
+ 'Wallet::Object::Duo::RadiusProxy new with no config failed');
+is ($@, "duo object implementation not configured\n", '...with correct error');
+$object = eval {
+ Wallet::Object::Duo::RadiusProxy->create ('duo-radius', 'test', $schema,
+ @trace);
+};
+is ($object, undef,
+ 'Wallet::Object::Duo::RadiusProxy creation with no config failed');
+is ($@, "duo object implementation not configured\n", '...with correct error');
+
+# Set up the Duo configuration.
+$Wallet::Config::DUO_AGENT = $mock;
+$Wallet::Config::DUO_KEY_FILE = 't/data/duo/keys.json';
+
+# Test creating an integration.
+note ('Test creating an integration');
+my $expected = {
+ name => 'test (radius)',
+ notes => 'Managed by wallet',
+ type => 'radius',
+};
+$mock->expect (
+ {
+ method => 'POST',
+ uri => '/admin/v1/integrations',
+ content => $expected,
+ response_file => 't/data/duo/integration-radius.json',
+ }
+);
+$object = Wallet::Object::Duo::RadiusProxy->create ('duo-radius', 'test',
+ $schema, @trace);
+isa_ok ($object, 'Wallet::Object::Duo::RadiusProxy');
+
+# Check the metadata about the new wallet object.
+$expected = <<"EOO";
+ Type: duo-radius
+ Name: test
+ Duo key: DIRWIH0ZZPV4G88B37VQ
+ Created by: $user
+ Created from: $host
+ Created on: $date
+EOO
+is ($object->show, $expected, 'Show output is correct');
+
+# Test retrieving the integration information.
+note ('Test retrieving an integration');
+$mock->expect (
+ {
+ method => 'GET',
+ uri => '/admin/v1/integrations/DIRWIH0ZZPV4G88B37VQ',
+ response_file => 't/data/duo/integration-radius.json',
+ }
+);
+my $data = $object->get (@trace);
+ok (defined ($data), 'Retrieval succeeds');
+$expected = <<'EOO';
+[radius_server_challenge]
+ikey = DIRWIH0ZZPV4G88B37VQ
+skey = QO4ZLqQVRIOZYkHfdPDORfcNf8LeXIbCWwHazY7o
+api_host = example-admin.duosecurity.com
+client = radius_client
+EOO
+is ($data, $expected, '...and integration data is correct');
+
+# Ensure that we can't retrieve the object when locked.
+is ($object->flag_set ('locked', @trace), 1,
+ 'Setting object to locked succeeds');
+is ($object->get, undef, '...and now get fails');
+is ($object->error, 'cannot get duo-radius:test: object is locked',
+ '...with correct error');
+is ($object->flag_clear ('locked', @trace), 1,
+ '...and clearing locked flag works');
+
+# Create a new object by wallet type and name.
+$object = Wallet::Object::Duo::RadiusProxy->new ('duo-radius', 'test',
+ $schema);
+
+# Test deleting an integration. We can't test this entirely properly because
+# currently Net::Duo::Mock::Agent doesn't support stacking multiple expected
+# calls and delete makes two calls.
+note ('Test deleting an integration');
+$mock->expect (
+ {
+ method => 'GET',
+ uri => '/admin/v1/integrations/DIRWIH0ZZPV4G88B37VQ',
+ response_file => 't/data/duo/integration.json',
+ }
+);
+TODO: {
+ local $TODO = 'Net::Duo::Mock::Agent not yet capable';
+
+ is ($object->destroy (@trace), 1, 'Duo object deletion succeeded');
+ $object = eval {
+ Wallet::Object::Duo::RadiusProxy->new ('duo-radius', 'test', $schema);
+ };
+ is ($object, undef, '...and now object cannot be retrieved');
+ is ($@, "cannot find duo:test\n", '...with correct error');
+}
+
+# Clean up.
+$admin->destroy;
+END {
+ unlink ('wallet-db');
+}
+
+# Done testing.
+done_testing ();
diff --git a/perl/t/object/duo-rdp.t b/perl/t/object/duo-rdp.t
new file mode 100644
index 0000000..9b2d566
--- /dev/null
+++ b/perl/t/object/duo-rdp.t
@@ -0,0 +1,158 @@
+#!/usr/bin/perl
+#
+# Tests for the Duo RDP integration object implementation.
+#
+# Written by Russ Allbery <eagle@eyrie.org>
+# Copyright 2014
+# The Board of Trustees of the Leland Stanford Junior University
+#
+# See LICENSE for licensing terms.
+
+use strict;
+use warnings;
+
+use POSIX qw(strftime);
+use Test::More;
+
+BEGIN {
+ eval 'use Net::Duo';
+ plan skip_all => 'Net::Duo required for testing duo'
+ if $@;
+ eval 'use Net::Duo::Mock::Agent';
+ plan skip_all => 'Net::Duo::Mock::Agent required for testing duo'
+ if $@;
+}
+
+BEGIN {
+ use_ok('Wallet::Admin');
+ use_ok('Wallet::Config');
+ use_ok('Wallet::Object::Duo::RDP');
+}
+
+use lib 't/lib';
+use Util;
+
+# Some global defaults to use.
+my $user = 'admin@EXAMPLE.COM';
+my $host = 'localhost';
+my @trace = ($user, $host, time);
+my $date = strftime ('%Y-%m-%d %H:%M:%S', localtime $trace[2]);
+
+# Flush all output immediately.
+$| = 1;
+
+# Use Wallet::Admin to set up the database.
+db_setup;
+my $admin = eval { Wallet::Admin->new };
+is ($@, '', 'Database connection succeeded');
+is ($admin->reinitialize ($user), 1, 'Database initialization succeeded');
+my $schema = $admin->schema;
+
+# Create a mock object to use for Duo calls.
+my $mock = Net::Duo::Mock::Agent->new ({ key_file => 't/data/duo/keys.json' });
+
+# Test error handling in the absence of configuration.
+my $object = eval {
+ Wallet::Object::Duo::RDP->new ('duo-rdp', 'test', $schema);
+};
+is ($object, undef, 'Wallet::Object::Duo::RDP new with no config failed');
+is ($@, "duo object implementation not configured\n", '...with correct error');
+$object = eval {
+ Wallet::Object::Duo::RDP->create ('duo-rdp', 'test', $schema, @trace);
+};
+is ($object, undef, 'Wallet::Object::Duo::RDP creation with no config failed');
+is ($@, "duo object implementation not configured\n", '...with correct error');
+
+# Set up the Duo configuration.
+$Wallet::Config::DUO_AGENT = $mock;
+$Wallet::Config::DUO_KEY_FILE = 't/data/duo/keys.json';
+
+# Test creating an integration.
+note ('Test creating an integration');
+my $expected = {
+ name => 'test (rdp)',
+ notes => 'Managed by wallet',
+ type => 'rdp',
+};
+$mock->expect (
+ {
+ method => 'POST',
+ uri => '/admin/v1/integrations',
+ content => $expected,
+ response_file => 't/data/duo/integration-rdp.json',
+ }
+);
+$object = Wallet::Object::Duo::RDP->create ('duo-rdp', 'test', $schema,
+ @trace);
+isa_ok ($object, 'Wallet::Object::Duo::RDP');
+
+# Check the metadata about the new wallet object.
+$expected = <<"EOO";
+ Type: duo-rdp
+ Name: test
+ Duo key: DIRWIH0ZZPV4G88B37VQ
+ Created by: $user
+ Created from: $host
+ Created on: $date
+EOO
+is ($object->show, $expected, 'Show output is correct');
+
+# Test retrieving the integration information.
+note ('Test retrieving an integration');
+$mock->expect (
+ {
+ method => 'GET',
+ uri => '/admin/v1/integrations/DIRWIH0ZZPV4G88B37VQ',
+ response_file => 't/data/duo/integration-rdp.json',
+ }
+);
+my $data = $object->get (@trace);
+ok (defined ($data), 'Retrieval succeeds');
+$expected = <<'EOO';
+Integration key: DIRWIH0ZZPV4G88B37VQ
+Secret key: QO4ZLqQVRIOZYkHfdPDORfcNf8LeXIbCWwHazY7o
+Host: example-admin.duosecurity.com
+EOO
+is ($data, $expected, '...and integration data is correct');
+
+# Ensure that we can't retrieve the object when locked.
+is ($object->flag_set ('locked', @trace), 1,
+ 'Setting object to locked succeeds');
+is ($object->get, undef, '...and now get fails');
+is ($object->error, 'cannot get duo-rdp:test: object is locked',
+ '...with correct error');
+is ($object->flag_clear ('locked', @trace), 1,
+ '...and clearing locked flag works');
+
+# Create a new object by wallet type and name.
+$object = Wallet::Object::Duo::RDP->new ('duo-rdp', 'test', $schema);
+
+# Test deleting an integration. We can't test this entirely properly because
+# currently Net::Duo::Mock::Agent doesn't support stacking multiple expected
+# calls and delete makes two calls.
+note ('Test deleting an integration');
+$mock->expect (
+ {
+ method => 'GET',
+ uri => '/admin/v1/integrations/DIRWIH0ZZPV4G88B37VQ',
+ response_file => 't/data/duo/integration-rdp.json',
+ }
+);
+TODO: {
+ local $TODO = 'Net::Duo::Mock::Agent not yet capable';
+
+ is ($object->destroy (@trace), 1, 'Duo object deletion succeeded');
+ $object = eval { Wallet::Object::Duo::RDP->new ('duo-rdp', 'test',
+ $schema) };
+ is ($object, undef, '...and now object cannot be retrieved');
+ is ($@, "cannot find duo:test\n", '...with correct error');
+}
+
+# Clean up.
+$admin->destroy;
+END {
+ unlink ('wallet-db');
+}
+
+# Done testing.
+done_testing ();
diff --git a/perl/t/object/duo.t b/perl/t/object/duo.t
index f73fe7e..a975597 100755
--- a/perl/t/object/duo.t
+++ b/perl/t/object/duo.t
@@ -108,10 +108,9 @@ $mock->expect (
my $data = $object->get (@trace);
ok (defined ($data), 'Retrieval succeeds');
$expected = <<'EOO';
-[duo]
-ikey = DIRWIH0ZZPV4G88B37VQ
-skey = QO4ZLqQVRIOZYkHfdPDORfcNf8LeXIbCWwHazY7o
-host = example-admin.duosecurity.com
+Integration key: DIRWIH0ZZPV4G88B37VQ
+Secret key: QO4ZLqQVRIOZYkHfdPDORfcNf8LeXIbCWwHazY7o
+Host: example-admin.duosecurity.com
EOO
is ($data, $expected, '...and integration data is correct');
diff --git a/perl/t/object/file.t b/perl/t/object/file.t
index 201f46d..b7f295a 100755
--- a/perl/t/object/file.t
+++ b/perl/t/object/file.t
@@ -12,7 +12,7 @@ use strict;
use warnings;
use POSIX qw(strftime);
-use Test::More tests => 56;
+use Test::More tests => 60;
use Wallet::Admin;
use Wallet::Config;
@@ -101,9 +101,15 @@ is ($object->error, 'data exceeds maximum of 1024 bytes',
is ($object->store ('', @trace), 1, 'Storing the empty object works');
is ($object->get (@trace), '', ' and get returns the right thing');
+# Test renaming a file object.
+is ($object->rename ('test-rename', @trace), 1, 'Renaming the object works');
+is ($object->{name}, 'test-rename', ' and the object is renamed');
+ok (-f 'test-files/2b/test-rename', ' and the file is in the new location');
+ok (! -f 'test-files/09/test', ' and nothing is in the old location');
+
# Test destruction.
is ($object->destroy (@trace), 1, 'Destroying the object works');
-ok (! -f 'test-files/09/test', ' and the file is gone');
+ok (! -f 'test-files/2b/test-rename', ' and the file is gone');
# Now try some aggressive names.
$object = eval {
diff --git a/server/keytab-backend.8 b/server/keytab-backend.8
index 8eb4c3d..b143e46 100644
--- a/server/keytab-backend.8
+++ b/server/keytab-backend.8
@@ -1,4 +1,4 @@
-.\" Automatically generated by Pod::Man 2.27 (Pod::Simple 3.28)
+.\" Automatically generated by Pod::Man 2.28 (Pod::Simple 3.28)
.\"
.\" Standard preamble:
.\" ========================================================================
@@ -133,7 +133,7 @@
.\" ========================================================================
.\"
.IX Title "KEYTAB-BACKEND 8"
-.TH KEYTAB-BACKEND 8 "2014-07-16" "1.1" "wallet"
+.TH KEYTAB-BACKEND 8 "2014-12-08" "1.2" "wallet"
.\" For nroff, turn off justification. Always turn off hyphenation; it makes
.\" way too many mistakes in technical documents.
.if n .ad l
diff --git a/server/wallet-admin.8 b/server/wallet-admin.8
index 64226f7..cc35d0e 100644
--- a/server/wallet-admin.8
+++ b/server/wallet-admin.8
@@ -1,4 +1,4 @@
-.\" Automatically generated by Pod::Man 2.27 (Pod::Simple 3.28)
+.\" Automatically generated by Pod::Man 2.28 (Pod::Simple 3.28)
.\"
.\" Standard preamble:
.\" ========================================================================
@@ -133,7 +133,7 @@
.\" ========================================================================
.\"
.IX Title "WALLET-ADMIN 8"
-.TH WALLET-ADMIN 8 "2014-07-16" "1.1" "wallet"
+.TH WALLET-ADMIN 8 "2014-12-08" "1.2" "wallet"
.\" For nroff, turn off justification. Always turn off hyphenation; it makes
.\" way too many mistakes in technical documents.
.if n .ad l
diff --git a/server/wallet-backend b/server/wallet-backend
index a2e6e6f..8dfc952 100755
--- a/server/wallet-backend
+++ b/server/wallet-backend
@@ -287,6 +287,9 @@ sub command {
failure ($server->error, @_);
}
}
+ } elsif ($command eq 'rename') {
+ check_args (3, 3, [], @args);
+ $server->rename (@args) or failure ($server->error, @_);
} elsif ($command eq 'setacl') {
check_args (4, 4, [], @args);
$server->acl (@args) or failure ($server->error, @_);
@@ -552,6 +555,12 @@ If <owner> is given, sets the owner of the object identified by <type> and
<name> to <owner>. If <owner> is the empty string, clears the owner of
the object.
+=item rename <type> <name> <new-name>
+
+Renames an existing object. This currently only supports file objects,
+where it renames the object itself, then the name and location of the
+object in the file store.
+
=item setacl <type> <name> <acl> <id>
Sets the ACL <acl>, which must be one of C<get>, C<store>, C<show>,
diff --git a/server/wallet-backend.8 b/server/wallet-backend.8
index b1c57d0..f1544ac 100644
--- a/server/wallet-backend.8
+++ b/server/wallet-backend.8
@@ -1,4 +1,4 @@
-.\" Automatically generated by Pod::Man 2.27 (Pod::Simple 3.28)
+.\" Automatically generated by Pod::Man 2.28 (Pod::Simple 3.28)
.\"
.\" Standard preamble:
.\" ========================================================================
@@ -133,7 +133,7 @@
.\" ========================================================================
.\"
.IX Title "WALLET-BACKEND 8"
-.TH WALLET-BACKEND 8 "2014-07-16" "1.1" "wallet"
+.TH WALLET-BACKEND 8 "2014-12-08" "1.2" "wallet"
.\" For nroff, turn off justification. Always turn off hyphenation; it makes
.\" way too many mistakes in technical documents.
.if n .ad l
@@ -320,6 +320,11 @@ result will be the name of an \s-1ACL.\s0
If <owner> is given, sets the owner of the object identified by <type> and
<name> to <owner>. If <owner> is the empty string, clears the owner of
the object.
+.IP "rename <type> <name> <new\-name>" 4
+.IX Item "rename <type> <name> <new-name>"
+Renames an existing object. This currently only supports file objects,
+where it renames the object itself, then the name and location of the
+object in the file store.
.IP "setacl <type> <name> <acl> <id>" 4
.IX Item "setacl <type> <name> <acl> <id>"
Sets the \s-1ACL\s0 <acl>, which must be one of \f(CW\*(C`get\*(C'\fR, \f(CW\*(C`store\*(C'\fR, \f(CW\*(C`show\*(C'\fR,
diff --git a/server/wallet-report.8 b/server/wallet-report.8
index f0ab9fd..ac3714f 100644
--- a/server/wallet-report.8
+++ b/server/wallet-report.8
@@ -1,4 +1,4 @@
-.\" Automatically generated by Pod::Man 2.27 (Pod::Simple 3.28)
+.\" Automatically generated by Pod::Man 2.28 (Pod::Simple 3.28)
.\"
.\" Standard preamble:
.\" ========================================================================
@@ -133,7 +133,7 @@
.\" ========================================================================
.\"
.IX Title "WALLET-REPORT 8"
-.TH WALLET-REPORT 8 "2014-07-16" "1.1" "wallet"
+.TH WALLET-REPORT 8 "2014-12-08" "1.2" "wallet"
.\" For nroff, turn off justification. Always turn off hyphenation; it makes
.\" way too many mistakes in technical documents.
.if n .ad l