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

(-)a/Koha/ApiKey.pm (-6 / +55 lines)
Lines 20-28 package Koha::ApiKey; Link Here
20
use Modern::Perl;
20
use Modern::Perl;
21
21
22
22
23
use Koha::Database;
23
use Koha::AuthUtils qw(hash_password);
24
use Koha::Exceptions;
24
use Koha::Exceptions::Object;
25
25
26
use List::MoreUtils qw(any);
26
use UUID;
27
use UUID;
27
28
28
use base qw(Koha::Object);
29
use base qw(Koha::Object);
Lines 46-59 Overloaded I<store> method. Link Here
46
sub store {
47
sub store {
47
    my ($self) = @_;
48
    my ($self) = @_;
48
49
49
    $self->client_id($self->_generate_unused_uuid('client_id'))
50
    if ( $self->in_storage ) {
50
        unless $self->client_id;
51
        my %dirty_columns = $self->_result->get_dirty_columns;
51
    $self->secret($self->_generate_unused_uuid('secret'))
52
52
        unless $self->secret;
53
        # only allow 'description' and 'active' to be updated
54
        for my $property ( keys %dirty_columns ) {
55
            Koha::Exceptions::Object::ReadOnlyProperty->throw( property => $property )
56
              if $property ne 'description' and $property ne 'active';
57
        }
58
    } else {
59
        $self->{_plain_text_secret} = $self->_generate_unused_uuid('secret');
60
        $self->set(
61
            {   secret    => Koha::AuthUtils::hash_password( $self->{_plain_text_secret} ),
62
                client_id => $self->_generate_unused_uuid('client_id'),
63
            }
64
        );
65
    }
53
66
54
    return $self->SUPER::store();
67
    return $self->SUPER::store();
55
}
68
}
56
69
70
=head3 validate_secret
71
72
    if ( $api_key->validate_secret( $secret ) ) { ... }
73
74
Returns a boolean that tells if the passed secret matches the one on the DB.
75
76
=cut
77
78
sub validate_secret {
79
    my ( $self, $secret ) = @_;
80
81
    my $digest = Koha::AuthUtils::hash_password( $secret, $self->secret );
82
83
    return $self->secret eq $digest;
84
}
85
86
=head3 plain_text_secret
87
88
    my $generated_secret = $api_key->store->plain_text_secret;
89
90
Returns the generated I<secret> so it can be displayed to  the end user.
91
This is only accessible when the object is new and has just been stored.
92
93
Returns I<undef> if the object was retrieved from the database.
94
95
=cut
96
97
sub plain_text_secret {
98
    my ($self) = @_;
99
100
    return $self->{_plain_text_secret}
101
        if $self->{_plain_text_secret};
102
103
    return;
104
}
105
57
=head2 Internal methods
106
=head2 Internal methods
58
107
59
=cut
108
=cut
(-)a/t/db_dependent/Koha/ApiKeys.t (-7 / +62 lines)
Lines 17-23 Link Here
17
17
18
use Modern::Perl;
18
use Modern::Perl;
19
19
20
use Test::More tests => 2;
20
use Test::More tests => 4;
21
use Test::MockModule;
21
use Test::MockModule;
22
use Test::Exception;
22
use Test::Exception;
23
23
Lines 30-41 BEGIN { Link Here
30
my $schema  = Koha::Database->new->schema;
30
my $schema  = Koha::Database->new->schema;
31
my $builder = t::lib::TestBuilder->new;
31
my $builder = t::lib::TestBuilder->new;
32
32
33
my $uuid_gen_client_id_counter = 0;
34
my $uuid_gen_secret_counter    = 0;
35
36
subtest 'store() tests' => sub {
33
subtest 'store() tests' => sub {
37
34
38
    plan tests => 13;
35
    plan tests => 16;
39
36
40
    $schema->storage->txn_begin;
37
    $schema->storage->txn_begin;
41
38
Lines 46-52 subtest 'store() tests' => sub { Link Here
46
43
47
    my $patron_1 = $builder->build_object( { class => 'Koha::Patrons' } );
44
    my $patron_1 = $builder->build_object( { class => 'Koha::Patrons' } );
48
    my $description = 'Coral API key';
45
    my $description = 'Coral API key';
49
    my $api_key = Koha::ApiKey->new({ patron_id => $patron_1->id, description => $description })->store;
46
47
    my $api_key = Koha::ApiKey->new(
48
        {
49
            patron_id   => $patron_1->id,
50
            description => $description
51
        }
52
    )->store;
50
53
51
    # re-read from DB
54
    # re-read from DB
52
    $api_key->discard_changes;
55
    $api_key->discard_changes;
Lines 75-80 subtest 'store() tests' => sub { Link Here
75
    is( $api_key->patron_id, $original_api_key->{patron_id}, '->store() preserves the patron_id' );
78
    is( $api_key->patron_id, $original_api_key->{patron_id}, '->store() preserves the patron_id' );
76
    is( $api_key->active, 0, '->store() preserves the active value' );
79
    is( $api_key->active, 0, '->store() preserves the active value' );
77
80
81
    $api_key->set({ client_id => 'NewID!' });
82
83
    throws_ok {
84
        $api_key->store
85
    }
86
    'Koha::Exceptions::Object::ReadOnlyProperty',
87
        'Read-only attribute overwrite attempt raises exception';
88
89
    is( $@->property, 'client_id', 'Correct attribute reported back' );
90
91
    $api_key->discard_changes;
92
    # set a writeable attribute
93
    $api_key->set({ description => 'Hey' });
94
    lives_ok { $api_key->store } 'Updating a writeable attribute works';
95
78
    my $patron_to_delete = $builder->build_object( { class => 'Koha::Patrons' } );
96
    my $patron_to_delete = $builder->build_object( { class => 'Koha::Patrons' } );
79
    my $deleted_id = $patron_to_delete->id;
97
    my $deleted_id = $patron_to_delete->id;
80
    $patron_to_delete->delete;
98
    $patron_to_delete->delete;
Lines 96-98 subtest 'store() tests' => sub { Link Here
96
114
97
    $schema->storage->txn_rollback;
115
    $schema->storage->txn_rollback;
98
};
116
};
99
- 
117
118
subtest 'validate_secret() tests' => sub {
119
120
    plan tests => 2;
121
122
    $schema->storage->txn_begin;
123
124
    my $patron  = $builder->build_object( { class => 'Koha::Patrons' } );
125
    my $api_key = Koha::ApiKey->new(
126
        {   patron_id   => $patron->id,
127
            description => 'The description'
128
        }
129
    )->store;
130
131
    my $secret = $api_key->plain_text_secret;
132
133
    ok( $api_key->validate_secret( $secret ), 'Valid secret returns true' );
134
    ok( !$api_key->validate_secret( 'Wrong secret' ), 'Invalid secret returns true' );
135
136
    $schema->storage->txn_rollback;
137
};
138
139
subtest 'plain_text_secret() tests' => sub {
140
141
    plan tests => 2;
142
143
    $schema->storage->txn_begin;
144
145
    my $patron = $builder->build_object({ class => 'Koha::Patrons' });
146
    # generate a fresh API key
147
    my $api_key = Koha::ApiKey->new({ description => 'blah', patron_id => $patron->id })->store;
148
    my $plain_text_secret = $api_key->plain_text_secret;
149
150
    ok( defined $plain_text_secret, 'A fresh API key carries its plain text secret' );
151
    ok( $plain_text_secret ne q{}, 'Plain text secret is not an empty string' );
152
153
    $schema->storage->txn_rollback;
154
};

Return to bug 28772