View | Details | Raw Unified | Return to bug 29894
Collapse All | Expand All

(-)a/Koha/Auth/TwoFactorAuth.pm (-13 / +26 lines)
Lines 16-25 package Koha::Auth::TwoFactorAuth; Link Here
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
17
18
use Modern::Perl;
18
use Modern::Perl;
19
use Auth::GoogleAuth;
20
use Imager::QRCode;
19
use Imager::QRCode;
21
use MIME::Base64 qw( encode_base64 );
20
use MIME::Base64 qw( encode_base64 );
22
21
22
use Koha::Exceptions;
23
23
use base qw( Auth::GoogleAuth );
24
use base qw( Auth::GoogleAuth );
24
25
25
=head1 NAME
26
=head1 NAME
Lines 42-71 It's based on Auth::GoogleAuth Link Here
42
43
43
    $obj = Koha::Auth::TwoFactorAuth->new({ patron => $p, secret => $s });
44
    $obj = Koha::Auth::TwoFactorAuth->new({ patron => $p, secret => $s });
44
45
46
    Patron is mandatory.
47
    Secret is optional, defaults to patron's secret.
48
    Passing secret32 overrules secret! Secret32 should be base32.
49
45
=cut
50
=cut
46
51
47
sub new {
52
sub new {
48
    my ($class, $params) = @_;
53
    my ($class, $params) = @_;
49
    my $patron   = $params->{patron};
54
    my $patron   = $params->{patron};
50
    my $secret   = $params->{secret};
51
    my $secret32 = $params->{secret32};
55
    my $secret32 = $params->{secret32};
52
56
    my $secret = $params->{secret};
53
    if (!$secret && !$secret32){
57
54
        $secret32 = $patron->secret;
58
    Koha::Exceptions::MissingParameter->throw("Mandatory patron parameter missing")
59
        unless $patron && ref($patron) eq 'Koha::Patron';
60
61
    my $type = 'secret32';
62
    if( $secret32 ) {
63
        Koha::Exceptions::BadParameter->throw("Secret32 should be base32")
64
            if $secret32 =~ /[^a-z2-7]/;
65
    } elsif( $secret ) {
66
        $type = 'secret';
67
    } elsif( $patron->secret ) {
68
        $secret32 = $patron->secret; # saved already in base32
69
    } else {
70
        Koha::Exceptions::MissingParameter->throw("No secret passed or patron has no secret");
55
    }
71
    }
56
72
57
    my $issuer = $patron->library->branchname;
73
    my $issuer = $patron->library->branchname;
58
    my $key_id = sprintf "%s_%s",
74
    my $key_id = sprintf "%s_%s",
59
      $issuer, ( $patron->email || $patron->userid );
75
      $issuer, ( $patron->email || $patron->userid );
60
76
61
    return $class->SUPER::new(
77
    return $class->SUPER::new({
62
        {
78
        $type => $secret32 || $secret,
63
            ( $secret   ? ( secret   => $secret )   : () ),
79
        issuer => $issuer,
64
            ( $secret32 ? ( secret32 => $secret32 ) : () ),
80
        key_id => $key_id,
65
            issuer => $issuer,
81
    });
66
            key_id => $key_id,
67
        }
68
    );
69
}
82
}
70
83
71
=head3 qr_code
84
=head3 qr_code
(-)a/members/two_factor_auth.pl (-1 / +1 lines)
Lines 17-23 Link Here
17
17
18
use Modern::Perl;
18
use Modern::Perl;
19
19
20
use CGI;
20
use CGI qw(-utf8);
21
21
22
use C4::Auth qw( get_template_and_user );
22
use C4::Auth qw( get_template_and_user );
23
use C4::Output qw( output_and_exit output_html_with_http_headers );
23
use C4::Output qw( output_and_exit output_html_with_http_headers );
(-)a/t/db_dependent/Koha/Auth/TwoFactorAuth.t (-16 / +44 lines)
Lines 1-5 Link Here
1
use Modern::Perl;
1
use Modern::Perl;
2
use Test::More tests => 1;
2
use Test::More tests => 2;
3
use Test::Exception;
3
4
4
use t::lib::Mocks;
5
use t::lib::Mocks;
5
use t::lib::TestBuilder;
6
use t::lib::TestBuilder;
Lines 10-40 use Koha::Auth::TwoFactorAuth; Link Here
10
our $schema = Koha::Database->new->schema;
11
our $schema = Koha::Database->new->schema;
11
our $builder = t::lib::TestBuilder->new;
12
our $builder = t::lib::TestBuilder->new;
12
13
13
subtest 'qr_code' => sub {
14
subtest 'new' => sub {
14
    plan tests => 9;
15
    plan tests => 10;
15
16
    $schema->storage->txn_begin;
16
    $schema->storage->txn_begin;
17
17
18
    t::lib::Mocks::mock_preference('TwoFactorAuthentication', 1);
18
    t::lib::Mocks::mock_preference('TwoFactorAuthentication', 1);
19
    my $patron = $builder->build_object({ class => 'Koha::Patrons' });
20
19
21
    # Testing without secret (might change later on)
20
    # Trivial test: no patron, no object
21
    throws_ok { Koha::Auth::TwoFactorAuth->new; }
22
        'Koha::Exceptions::MissingParameter',
23
        'Croaked on missing patron';
24
    throws_ok { Koha::Auth::TwoFactorAuth->new({ patron => 'Henk', secret => q<> }) }
25
        'Koha::Exceptions::MissingParameter',
26
        'Croaked on missing patron object';
27
28
    # Testing without secret
29
    my $patron = $builder->build_object({ class => 'Koha::Patrons' });
22
    is( $patron->secret, undef, 'Secret still undefined' );
30
    is( $patron->secret, undef, 'Secret still undefined' );
31
    throws_ok { Koha::Auth::TwoFactorAuth->new({ patron => $patron }) }
32
        'Koha::Exceptions::MissingParameter', 'Croaks on missing secret';
33
    # Pass a wrong encoded secret
34
    throws_ok { Koha::Auth::TwoFactorAuth->new({ patron => $patron, secret32 => '@' }) }
35
        'Koha::Exceptions::BadParameter',
36
        'Croaked on wrong encoding';
37
38
    # Test passing secret or secret32 (converted to base32)
39
    $patron->secret('nv4v65dpobpxgzldojsxiii'); # this is base32 already for 'my_top_secret!'
23
    my $auth = Koha::Auth::TwoFactorAuth->new({ patron => $patron });
40
    my $auth = Koha::Auth::TwoFactorAuth->new({ patron => $patron });
24
    is( $auth->secret, undef, 'Still no secret yet as expected' );
41
    is( $auth->secret32, $patron->secret, 'Base32 secret as expected' );
25
    # Auth::GoogleAuth will generate a secret when calling qr_code
42
    $auth->code( $patron->secret ); # trigger conversion by passing base32 to code
26
    my $img_data = $auth->qr_code;
43
    is( $auth->secret, 'my_top_secret!', 'Decoded secret fine too' );
27
    is( length($auth->secret32), 16, 'Secret of 16 base32 chars expected' );
44
    # The other way around
28
    is( length($img_data) > 22, 1, 'Dataurl not empty too' ); # prefix is 22
45
    $auth = Koha::Auth::TwoFactorAuth->new({ patron => $patron, secret => 'my_top_secret!' });
29
    $auth->clear;
46
    is( $auth->secret32, undef, 'GoogleAuth did not yet encode' );
47
    $auth->code; # this will trigger base32 encoding now
48
    is( $auth->secret, 'my_top_secret!', 'Check secret' );
49
    is( $auth->secret32, 'nv4v65dpobpxgzldojsxiii', 'Check secret32' );
50
51
    $schema->storage->txn_rollback;
52
};
53
54
subtest 'qr_code' => sub {
55
    plan tests => 5;
56
57
    $schema->storage->txn_begin;
30
58
31
    # Update patron data
59
    t::lib::Mocks::mock_preference('TwoFactorAuthentication', 1);
60
    my $patron = $builder->build_object({ class => 'Koha::Patrons' });
32
    $patron->secret('you2wont2guess2it'); # this is base32 btw
61
    $patron->secret('you2wont2guess2it'); # this is base32 btw
33
    $patron->auth_method('two-factor');
62
    $patron->auth_method('two-factor');
34
    $patron->store;
63
    $patron->store;
35
64
36
    $auth = Koha::Auth::TwoFactorAuth->new({ patron => $patron });
65
    my $auth = Koha::Auth::TwoFactorAuth->new({ patron => $patron });
37
    $img_data = $auth->qr_code;
66
    my $img_data = $auth->qr_code;
38
    is( substr($img_data, 0, 22), 'data:image/png;base64,', 'Checking prefix of dataurl' );
67
    is( substr($img_data, 0, 22), 'data:image/png;base64,', 'Checking prefix of dataurl' );
39
    like( substr($img_data, 22), qr/^[a-zA-Z0-9\/=+]+$/, 'Contains base64 chars' );
68
    like( substr($img_data, 22), qr/^[a-zA-Z0-9\/=+]+$/, 'Contains base64 chars' );
40
    is( $auth->qr_code, $img_data, 'Repeated call' );
69
    is( $auth->qr_code, $img_data, 'Repeated call' );
41
- 

Return to bug 29894