Bug 25623: Make sure oauth.t rolls back the db
[koha.git] / t / db_dependent / api / v1 / oauth.t
1 #!/usr/bin/env perl
2
3 # This file is part of Koha.
4 #
5 # Koha is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # Koha is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18 use Modern::Perl;
19
20 use Test::More;
21 use Test::MockModule;
22 use Test::Mojo;
23
24 use MIME::Base64;
25 use Module::Load::Conditional qw(can_load);
26
27 use Koha::ApiKeys;
28 use Koha::Database;
29 use Koha::Patrons;
30
31 use t::lib::Mocks;
32 use t::lib::TestBuilder;
33
34 my $t = Test::Mojo->new('Koha::REST::V1');
35 my $schema  = Koha::Database->new->schema;
36 my $builder = t::lib::TestBuilder->new();
37
38 if ( can_load( modules => { 'Net::OAuth2::AuthorizationServer' => undef } ) ) {
39     plan tests => 2;
40 }
41 else {
42     plan skip_all => 'Net::OAuth2::AuthorizationServer not available';
43 }
44
45 subtest '/oauth/token tests' => sub {
46
47     plan tests => 10;
48
49     t::lib::Mocks::mock_preference('RESTOAuth2ClientCredentials', 0);
50
51     # Missing parameter grant_type
52     $t->post_ok('/api/v1/oauth/token')
53         ->status_is(400);
54
55     # Wrong grant type
56     $t->post_ok('/api/v1/oauth/token', form => { grant_type => 'password' })
57         ->status_is(400)
58         ->json_is({error => 'Unimplemented grant type'});
59
60     t::lib::Mocks::mock_preference('RESTOAuth2ClientCredentials', 1);
61
62     # No client_id/client_secret
63     $t->post_ok('/api/v1/oauth/token', form => { grant_type => 'client_credentials' })
64         ->status_is(403)
65         ->json_is({error => 'unauthorized_client'});
66
67     subtest 'Client credentials in body' => sub {
68
69         plan tests => 19;
70
71         run_oauth_tests( 'body' );
72     };
73
74     subtest 'Client credentials in Authorization header' => sub {
75
76
77         plan tests => 19;
78
79         run_oauth_tests( 'header' );
80     };
81 };
82
83 subtest 'Net::OAuth2::AuthorizationServer missing tests' => sub {
84
85     plan tests => 10;
86
87     $schema->storage->txn_begin;
88
89     my $load_conditional = Test::MockModule->new('Module::Load::Conditional');
90
91     # Enable the client credentials grant syspref
92     t::lib::Mocks::mock_preference( 'RESTOAuth2ClientCredentials', 1 );
93
94     my $patron = $builder->build_object({ class => 'Koha::Patrons', value => { flags => 2**4 } });
95     my $api_key = Koha::ApiKey->new({ patron_id => $patron->id, description => 'blah' })->store;
96
97     my $form_data = {
98         grant_type    => 'client_credentials',
99         client_id     => $api_key->client_id,
100         client_secret => $api_key->secret
101     };
102
103     $t->post_ok( '/api/v1/oauth/token', form => $form_data )->status_is(200)
104       ->json_is( '/expires_in' => 3600 )->json_is( '/token_type' => 'Bearer' )
105       ->json_has('/access_token');
106
107     my $access_token = $t->tx->res->json->{access_token};
108
109     $load_conditional->mock( 'can_load', sub { return 0; } );
110
111     my $tx = $t->ua->build_tx( GET => '/api/v1/patrons' );
112     $tx->req->headers->authorization("Bearer $access_token");
113     $t->request_ok($tx)
114       ->status_is(403);
115
116     $t->post_ok( '/api/v1/oauth/token', form => $form_data )
117       ->status_is(400)
118       ->json_is( { error => 'Unimplemented grant type' } );
119
120     $schema->storage->txn_rollback;
121 };
122
123 sub run_oauth_tests {
124     my ( $test_case ) = @_;
125
126     $schema->storage->txn_begin;
127
128     my $patron = $builder->build_object({
129         class => 'Koha::Patrons',
130         value  => {
131             flags => 0 # no permissions
132         },
133     });
134
135     my $api_key = Koha::ApiKey->new({ patron_id => $patron->id, description => 'blah' })->store;
136
137     t::lib::Mocks::mock_preference( 'RESTOAuth2ClientCredentials', 1 );
138
139     my $formData;
140     my $client_id     = $api_key->client_id;
141     my $client_secret = $api_key->secret;
142
143     if ( $test_case eq 'header' ) {
144
145         $formData = { grant_type => 'client_credentials' };
146
147         $t->post_ok("//$client_id:$client_secret@/api/v1/oauth/token", form => $formData)
148           ->status_is(200)
149           ->json_is('/expires_in' => 3600)
150           ->json_is('/token_type' => 'Bearer')
151           ->json_has('/access_token');
152     } else {
153
154         $formData = {
155             grant_type    => 'client_credentials',
156             client_id     => $api_key->client_id,
157             client_secret => $api_key->secret
158         };
159
160         $t->post_ok('/api/v1/oauth/token', form => $formData)
161           ->status_is(200)
162           ->json_is('/expires_in' => 3600)
163           ->json_is('/token_type' => 'Bearer')
164           ->json_has('/access_token');
165     }
166
167     my $access_token = $t->tx->res->json->{access_token};
168
169     # Without access token, it returns 401
170     $t->get_ok('/api/v1/patrons')->status_is(401);
171
172     # With access token, but without permissions, it returns 403
173     my $tx = $t->ua->build_tx(GET => '/api/v1/patrons');
174     $tx->req->headers->authorization("Bearer $access_token");
175     $t->request_ok($tx)->status_is(403);
176
177     # With access token and permissions, it returns 200
178     $patron->flags(2**4)->store;
179     $tx = $t->ua->build_tx(GET => '/api/v1/patrons');
180     $tx->req->headers->authorization("Bearer $access_token");
181     $t->request_ok($tx)->status_is(200);
182
183     # expire token
184     my $token = Koha::OAuthAccessTokens->find($access_token);
185     $token->expires( time - 1 )->store;
186     $tx = $t->ua->build_tx( GET => '/api/v1/patrons' );
187     $tx->req->headers->authorization("Bearer $access_token");
188     $t->request_ok($tx)
189       ->status_is(401);
190
191     # revoke key
192     $api_key->active(0)->store;
193
194     if ( $test_case eq 'header' ) {
195         $t->post_ok("//$client_id:$client_secret@/api/v1/oauth/token", form => $formData)
196           ->status_is(403)
197           ->json_is({ error => 'unauthorized_client' });
198     } else {
199         $t->post_ok('/api/v1/oauth/token', form => $formData)
200           ->status_is(403)
201           ->json_is({ error => 'unauthorized_client' });
202     }
203
204     # disable client credentials grant
205    t::lib::Mocks::mock_preference('RESTOAuth2ClientCredentials', 0);
206
207     # enable API key
208     $api_key->active(1)->store;
209
210     # Wrong grant type
211     if ( $test_case eq 'header' ) {
212         $t->post_ok("//$client_id:$client_secret@/api/v1/oauth/token", form => $formData )
213           ->status_is(400)
214           ->json_is({ error => 'Unimplemented grant type' });
215     }
216     else {
217         $t->post_ok('/api/v1/oauth/token', form => $formData )
218           ->status_is(400)
219           ->json_is({ error => 'Unimplemented grant type' });
220     }
221
222     $schema->storage->txn_rollback;
223 }