Koha-clms/t/db_dependent/Koha/Auth/TwoFactorAuth.t

128 lines
5.0 KiB
Perl
Executable File

#!/usr/bin/perl
use Modern::Perl;
use Test::More tests => 4;
use Test::NoWarnings;
use Test::Exception;
use Test::MockModule;
use t::lib::Mocks;
use t::lib::TestBuilder;
use Koha::Database;
use Koha::Auth::TwoFactorAuth;
use Koha::Exceptions;
use Koha::Exceptions::Patron;
use Koha::Notice::Messages;
our $schema = Koha::Database->new->schema;
our $builder = t::lib::TestBuilder->new;
our $mocked_stuffer = Test::MockModule->new('Email::Stuffer');
$mocked_stuffer->mock( 'send_or_die', sub { warn 'I do not send mails now'; } );
subtest 'new' => sub {
plan tests => 10;
$schema->storage->txn_begin;
t::lib::Mocks::mock_preference( 'TwoFactorAuthentication', 'enabled' );
t::lib::Mocks::mock_config( 'encryption_key', 'bad_example' );
# Trivial test: no patron, no object
throws_ok { Koha::Auth::TwoFactorAuth->new; }
'Koha::Exceptions::MissingParameter',
'Croaked on missing patron';
throws_ok { Koha::Auth::TwoFactorAuth->new( { patron => 'Henk', secret => q<> } ) }
'Koha::Exceptions::MissingParameter',
'Croaked on missing patron object';
# Testing without secret
my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
is( $patron->secret, undef, 'Secret still undefined' );
throws_ok { Koha::Auth::TwoFactorAuth->new( { patron => $patron } ) }
'Koha::Exceptions::MissingParameter', 'Croaks on missing secret';
# Pass a wrong encoded secret
throws_ok { Koha::Auth::TwoFactorAuth->new( { patron => $patron, secret32 => '@' } ) }
'Koha::Exceptions::BadParameter',
'Croaked on wrong encoding';
# Test passing secret or secret32 (converted to base32)
$patron->encode_secret('nv4v65dpobpxgzldojsxiii'); # this is base32 already for 'my_top_secret!'
my $auth = Koha::Auth::TwoFactorAuth->new( { patron => $patron } );
is( $auth->secret32, $patron->decoded_secret, 'Base32 secret as expected' );
$auth->code( $patron->decoded_secret ); # trigger conversion by passing base32 to code
is( $auth->secret, 'my_top_secret!', 'Decoded secret fine too' );
# The other way around
$auth = Koha::Auth::TwoFactorAuth->new( { patron => $patron, secret => 'my_top_secret!' } );
is( $auth->secret32, undef, 'GoogleAuth did not yet encode' );
$auth->code; # this will trigger base32 encoding now
is( $auth->secret, 'my_top_secret!', 'Check secret' );
is( $auth->secret32, 'nv4v65dpobpxgzldojsxiii', 'Check secret32' );
$schema->storage->txn_rollback;
};
subtest 'qr_code' => sub {
plan tests => 5;
$schema->storage->txn_begin;
t::lib::Mocks::mock_preference( 'TwoFactorAuthentication', 'enabled' );
t::lib::Mocks::mock_config( 'encryption_key', 'bad_example' );
my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
$patron->encode_secret('you2wont2guess2it'); # this is base32 btw
$patron->auth_method('two-factor');
$patron->store;
my $auth = Koha::Auth::TwoFactorAuth->new( { patron => $patron } );
my $img_data = $auth->qr_code;
is( substr( $img_data, 0, 22 ), 'data:image/png;base64,', 'Checking prefix of dataurl' );
like( substr( $img_data, 22 ), qr/^[a-zA-Z0-9\/=+]+$/, 'Contains base64 chars' );
is( $auth->qr_code, $img_data, 'Repeated call' );
$auth->clear;
# Changing the secret should generate different data, right?
$patron->encode_secret('no3really3not3cracked'); # base32
$patron->store;
$auth = Koha::Auth::TwoFactorAuth->new( { patron => $patron } );
my $img_data02 = $auth->qr_code;
is( length($img_data02) > 22, 1, 'More characters than prefix' );
isnt( $img_data02, $img_data, 'Another secret, another image' );
$schema->storage->txn_rollback;
};
subtest 'verify' => sub {
plan tests => 4;
$schema->storage->txn_begin;
t::lib::Mocks::mock_preference( 'TwoFactorAuthentication', 'enabled' );
t::lib::Mocks::mock_config( 'encryption_key', 'bad_example' );
t::lib::Mocks::mock_config( 'mfa_range', undef );
my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
$patron->encode_secret('6557s35ui2gyhkmuuvei6rpk44'); # this is base32 btw
$patron->auth_method('two-factor');
$patron->store;
my $auth = Koha::Auth::TwoFactorAuth->new( { patron => $patron } );
my $code = 394342;
my $timestamp = 1694137671;
my $verified;
$verified = $auth->verify( $code, undef, undef, $timestamp, undef );
is( $verified, 1, "code valid within 1 minute" );
$verified = $auth->verify( $code, undef, undef, ( $timestamp + 60 ), undef );
is( $verified, 0, "code invalid within 2 minutes" );
t::lib::Mocks::mock_config( 'mfa_range', 10 );
$verified = $auth->verify( $code, undef, undef, ( $timestamp + 300 ), undef );
is( $verified, 1, "code valid within 5 minutes" );
$verified = $auth->verify( $code, undef, undef, ( $timestamp + 330 ), undef );
is( $verified, 0, "code valid within 5.5 minutes" );
$schema->storage->txn_rollback;
};