#!/usr/bin/perl
#
# Tests for the WebAuth keyring object implementation.
#
# Written by Russ Allbery <eagle@eyrie.org>
# Copyright 2013-2014
#     The Board of Trustees of the Leland Stanford Junior University
#
# SPDX-License-Identifier: MIT

use strict;
use warnings;

use Test::More;

BEGIN {
    eval 'use WebAuth 3.06 qw(WA_KEY_AES WA_AES_128)';
    plan skip_all => 'WebAuth 3.06 required for testing wa-keyring'
      if $@;
}

use WebAuth::Key 1.01 ();
use WebAuth::Keyring 1.02 ();

BEGIN {
    plan tests => 68;
    use_ok('Wallet::Admin');
    use_ok('Wallet::Config');
    use_ok('Wallet::Object::WAKeyring');
}

use lib 't/lib';
use Util;

# Some global defaults to use.
my $user = 'admin@EXAMPLE.COM';
my $host = 'localhost';
my @trace = ($user, $host, time);

# Flush all output immediately.
$| = 1;

# Use Wallet::Admin to set up the database.
system ('rm -rf test-keyrings') == 0 or die "cannot remove test-keyrings\n";
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 WebAuth context to use.
my $wa = WebAuth->new;

# Test error handling in the absence of configuration.
my $object = eval {
    Wallet::Object::WAKeyring->create ('wa-keyring', 'test', $schema, @trace)
  };
ok (defined ($object), 'Creating a basic WebAuth keyring object succeeds');
ok ($object->isa ('Wallet::Object::WAKeyring'), ' and is the right class');
is ($object->get (@trace), undef, ' and get fails');
is ($object->error, 'WebAuth keyring support not configured',
    ' with the right error');
is ($object->store (@trace), undef, ' and store fails');
is ($object->error, 'WebAuth keyring support not configured',
    ' with the right error');
is ($object->destroy (@trace), 1, ' but destroy succeeds');

# Set up our configuration.
mkdir 'test-keyrings' or die "cannot create test-keyrings: $!\n";
$Wallet::Config::WAKEYRING_BUCKET = 'test-keyrings';

# Okay, now we can test.  First, the basic object without store.
$object = eval {
    Wallet::Object::WAKeyring->create ('wa-keyring', 'test', $schema, @trace)
  };
ok (defined ($object), 'Creating a basic WebAuth keyring object succeeds');
ok ($object->isa ('Wallet::Object::WAKeyring'), ' and is the right class');
my $data = $object->get (@trace);
ok ($data, ' and get succeeds');
my $keyring = WebAuth::Keyring->decode ($wa, $data);
ok ($keyring->isa ('WebAuth::Keyring'), ' and resulting keyring decodes');
my @entries = $keyring->entries;
is (scalar (@entries), 3, ' and has three entries');
is ($entries[0]->creation, 0, 'First has good creation');
is ($entries[0]->key->type, WA_KEY_AES, ' and key type');
is ($entries[0]->key->length, WA_AES_128, ' and key length');
is ($entries[0]->valid_after, 0, ' and validity');
ok ((time - $entries[1]->creation) < 2, 'Second has good creation');
is ($entries[1]->key->type, WA_KEY_AES, ' and key type');
is ($entries[1]->key->length, WA_AES_128, ' and key length');
ok (($entries[1]->valid_after - time) <= 60 * 60 * 24,
    ' and validity (upper)');
ok (($entries[1]->valid_after - time) > 60 * 60 * 24 - 2,
    ' and validity (lower)');
ok ((time - $entries[2]->creation) < 2, 'Third has good creation');
is ($entries[2]->key->type, WA_KEY_AES, ' and key type');
is ($entries[2]->key->length, WA_AES_128, ' and key length');
ok (($entries[2]->valid_after - time) <= 2 * 60 * 60 * 24,
    ' and validity (upper)');
ok (($entries[2]->valid_after - time) > 2 * 60 * 60 * 24 - 2,
    ' and validity (lower)');
my $data2 = $object->get (@trace);
is ($data2, $data, 'Getting the object again returns the same data');
is ($object->error, undef, ' with no error');
is ($object->destroy (@trace), 1, 'Destroying the object succeeds');

# Now store something and be sure that we get something reasonable.
$object = eval {
    Wallet::Object::WAKeyring->create ('wa-keyring', 'test', $schema, @trace)
  };
ok (defined ($object), 'Recreating the object succeeds');
my $key = WebAuth::Key->new ($wa, WA_KEY_AES, WA_AES_128);
$keyring = WebAuth::Keyring->new ($wa, $key);
$data = $keyring->encode;
is ($object->store ($data, @trace), 1, ' and storing data in it succeeds');
ok (-d 'test-keyrings/09', ' and the hash bucket was created');
ok (-f 'test-keyrings/09/test', ' and the file exists');
is (contents ('test-keyrings/09/test'), $data, ' with the right contents');
$data = $object->get (@trace);
$keyring = WebAuth::Keyring->decode ($wa, $data);
ok ($keyring->isa ('WebAuth::Keyring'), ' and get returns a valid keyring');
@entries = $keyring->entries;
is (scalar (@entries), 2, ' and has three entries');
is ($entries[0]->creation, 0, 'First has good creation');
is ($entries[0]->key->type, WA_KEY_AES, ' and key type');
is ($entries[0]->key->length, WA_AES_128, ' and key length');
is ($entries[0]->valid_after, 0, ' and validity');
is ($entries[0]->key->data, $key->data, ' and matches the original key');
ok ((time - $entries[1]->creation) < 2, 'Second has good creation');
is ($entries[1]->key->type, WA_KEY_AES, ' and key type');
is ($entries[1]->key->length, WA_AES_128, ' and key length');
ok (($entries[1]->valid_after - time) <= 2 * 60 * 60 * 24,
    ' and validity (upper)');
ok (($entries[1]->valid_after - time) > 2 * 60 * 60 * 24 - 2,
    ' and validity (lower)');

# Test pruning.  Add another old key and a couple of more current keys to the
# current keyring.
$key = WebAuth::Key->new ($wa, WA_KEY_AES, WA_AES_128);
$keyring->add (0, 0, $key);
$key = WebAuth::Key->new ($wa, WA_KEY_AES, WA_AES_128);
$keyring->add (time - 24 * 60 * 60, time - 24 * 60 * 60, $key);
$key = WebAuth::Key->new ($wa, WA_KEY_AES, WA_AES_128);
$keyring->add (time, time, $key);
$data = $keyring->encode;
is ($object->store ($data, @trace), 1, 'Storing modified keyring succeeds');
$data = $object->get (@trace);
$keyring = WebAuth::Keyring->decode ($wa, $data);
ok ($keyring->isa ('WebAuth::Keyring'), ' and get returns a valid keyring');
@entries = $keyring->entries;
is (scalar (@entries), 3, ' and has three entries');
ok ((time - $entries[0]->creation) < 2, 'First has good creation');
ok (($entries[0]->valid_after - time) <= 2 * 60 * 60 * 24,
    ' and validity (upper)');
ok (($entries[0]->valid_after - time) > 2 * 60 * 60 * 24 - 2,
    ' and validity (lower)');
ok ((time - $entries[1]->creation) < 24 * 60 * 60 + 2,
    'Second has good creation');
ok ((time - $entries[1]->valid_after) <= 60 * 60 * 24 + 2,
    ' and validity');
ok ((time - $entries[2]->creation) < 2, 'Third has good creation');
ok ((time - $entries[2]->valid_after) < 2, ' and validity');
is ($object->destroy (@trace), 1, 'Destroying the object succeeds');

# Test error handling in the file store.
system ('rm -r test-keyrings') == 0 or die "cannot remove test-keyrings\n";
$object = eval {
    Wallet::Object::WAKeyring->create ('wa-keyring', 'test', $schema, @trace)
  };
ok (defined ($object), 'Recreating the object succeeds');
is ($object->get (@trace), undef, ' but retrieving it fails');
like ($object->error, qr/^cannot create keyring bucket 09: /,
      ' with the right error');
is ($object->store ("foo\n", @trace), undef, ' and store fails');
like ($object->error, qr/^cannot create keyring bucket 09: /,
      ' with the right error');
is ($object->destroy (@trace), 1, ' but destroying the object succeeds');

# Clean up.
$admin->destroy;
END {
    unlink ('wallet-db');
}