Bug 33146: Unit tests
[koha-ffzg.git] / t / db_dependent / api / v1 / two_factor_auth.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 tests => 2;
21 use Test::Mojo;
22 use Test::MockModule;
23
24 use t::lib::TestBuilder;
25 use t::lib::Mocks;
26
27 use Koha::Database;
28
29 my $schema  = Koha::Database->new->schema;
30 my $builder = t::lib::TestBuilder->new;
31
32 # FIXME: sessionStorage defaults to mysql, but it seems to break transaction handling
33 # this affects the other REST api tests
34 t::lib::Mocks::mock_preference( 'SessionStorage', 'tmp' );
35
36 my $remote_address = '127.0.0.1';
37 my $t              = Test::Mojo->new('Koha::REST::V1');
38
39 my $mocked_koha_email = Test::MockModule->new('Koha::Email');
40 $mocked_koha_email->mock( 'send_or_die', sub {
41     return 1;
42 });
43
44 subtest 'registration and verification' => sub {
45
46     plan tests => 22;
47
48     $schema->storage->txn_begin;
49
50     t::lib::Mocks::mock_preference('TwoFactorAuthentication', 'enabled');
51
52     my $patron = $builder->build_object(
53         {
54             class => 'Koha::Patrons',
55             value  => {
56                 flags => 20, # Staff access and Patron info
57             }
58         }
59     );
60
61     # Not authenticated yet - 401
62     my $session = C4::Auth::get_session('');
63     $session->param( 'ip',       '127.0.0.1' );
64     $session->param( 'lasttime', time() );
65     $session->flush;
66
67     my $tx = $t->ua->build_tx( POST => "/api/v1/auth/two-factor/registration" );
68     $tx->req->cookies( { name => 'CGISESSID', value => $session->id } );
69     $tx->req->env( { REMOTE_ADDR => $remote_address } );
70     $t->request_ok($tx)->status_is(401);
71
72     $tx = $t->ua->build_tx( POST => "/api/v1/auth/two-factor/registration/verification" );
73     $tx->req->cookies( { name => 'CGISESSID', value => $session->id } );
74     $tx->req->env( { REMOTE_ADDR => $remote_address } );
75     $t->request_ok($tx)->status_is(401);
76
77     # Authenticated - can register
78     $session->param( 'number',   $patron->borrowernumber );
79     $session->param( 'id',       $patron->userid );
80     $session->flush;
81
82     $patron->auth_method('password');
83     $patron->secret(undef);
84     $patron->store;
85
86     $tx = $t->ua->build_tx( POST => "/api/v1/auth/two-factor/registration" );
87     $tx->req->cookies( { name => 'CGISESSID', value => $session->id } );
88     $tx->req->env( { REMOTE_ADDR => $remote_address } );
89     $t->request_ok($tx)->status_is(201);
90     my $secret32 = $t->tx->res->json->{secret32};
91
92     $tx = $t->ua->build_tx( POST => "/api/v1/auth/two-factor/registration/verification" );
93     $tx->req->cookies( { name => 'CGISESSID', value => $session->id } );
94     $tx->req->env( { REMOTE_ADDR => $remote_address } );
95     $t->request_ok($tx)->status_is(400); # Missing parameter
96
97     my $auth = Koha::Auth::TwoFactorAuth->new({patron => $patron, secret32 => $secret32});
98     my $pin_code = $auth->code;
99     $tx = $t->ua->build_tx( POST => "/api/v1/auth/two-factor/registration/verification" => form => {secret32 => $secret32, pin_code => $pin_code} );
100     $tx->req->cookies( { name => 'CGISESSID', value => $session->id } );
101     $tx->req->env( { REMOTE_ADDR => $remote_address } );
102     $t->request_ok($tx)->status_is(204);
103
104     $patron = $patron->get_from_storage;
105     is($patron->auth_method, 'two-factor');
106     isnt($patron->secret, undef);
107
108     $patron->auth_method('password');
109     $patron->secret(undef);
110     $patron->store;
111
112     # Setting up 2FA - can register
113     $session->param('waiting-for-2FA-setup', 1);
114     $session->flush;
115     $tx = $t->ua->build_tx( POST => "/api/v1/auth/two-factor/registration" );
116     $tx->req->cookies( { name => 'CGISESSID', value => $session->id } );
117     $tx->req->env( { REMOTE_ADDR => $remote_address } );
118     $t->request_ok($tx)->status_is(201);
119     $secret32 = $t->tx->res->json->{secret32};
120
121     $auth = Koha::Auth::TwoFactorAuth->new({patron => $patron, secret32 => $secret32});
122     $pin_code = $auth->code;
123     $tx = $t->ua->build_tx( POST => "/api/v1/auth/two-factor/registration/verification" => form => {secret32 => $secret32, pin_code => $pin_code} );
124     $tx->req->cookies( { name => 'CGISESSID', value => $session->id } );
125     $tx->req->env( { REMOTE_ADDR => $remote_address } );
126     $t->request_ok($tx)->status_is(204);
127
128     $patron = $patron->get_from_storage;
129     is($patron->auth_method, 'two-factor');
130     isnt($patron->secret, undef);
131
132     # 2FA already enabled - cannot register again
133     $patron->auth_method('two-factor');
134     $patron->encode_secret("nv4v65dpobpxgzldojsxiii");
135     $patron->store;
136
137     $session->param('waiting-for-2FA-setup', undef);
138     $session->flush;
139
140     $tx = $t->ua->build_tx( POST => "/api/v1/auth/two-factor/registration" );
141     $tx->req->cookies( { name => 'CGISESSID', value => $session->id } );
142     $tx->req->env( { REMOTE_ADDR => $remote_address } );
143     $t->request_ok($tx)->status_is(401);
144
145     $tx = $t->ua->build_tx( POST => "/api/v1/auth/two-factor/registration/verification" );
146     $tx->req->cookies( { name => 'CGISESSID', value => $session->id } );
147     $tx->req->env( { REMOTE_ADDR => $remote_address } );
148     $t->request_ok($tx)->status_is(401);
149
150     $schema->storage->txn_rollback;
151 };
152
153 subtest 'send_otp_token' => sub {
154
155     plan tests => 11;
156
157     $schema->storage->txn_begin;
158
159     t::lib::Mocks::mock_preference('TwoFactorAuthentication', 'enabled');
160
161     my $patron = $builder->build_object(
162         {
163             class => 'Koha::Patrons',
164             value  => {
165                 flags => 20, # Staff access and Patron info
166             }
167         }
168     );
169
170     my $session = C4::Auth::get_session('');
171     $session->param( 'ip',       '127.0.0.1' );
172     $session->param( 'lasttime', time() );
173     $session->flush;
174
175     my $tx = $t->ua->build_tx( POST => "/api/v1/auth/otp/token_delivery" );
176     $tx->req->cookies( { name => 'CGISESSID', value => $session->id } );
177     $tx->req->env( { REMOTE_ADDR => $remote_address } );
178
179     # Session is still anonymous, no borrowernumber yet: Unauthorized
180     $t->request_ok($tx)->status_is(401);
181
182     # Patron is partially authenticated (credentials correct)
183     $session->param( 'number',   $patron->borrowernumber );
184     $session->param( 'id',       $patron->userid );
185     $session->param('waiting-for-2FA', 1);
186     $session->flush;
187
188     $patron->library->set(
189         {
190             branchemail      => 'from@example.org',
191             branchreturnpath => undef,
192             branchreplyto    => undef,
193         }
194     )->store;
195     $patron->auth_method('two-factor');
196     $patron->encode_secret("nv4v65dpobpxgzldojsxiii");
197     $patron->email(undef);
198     $patron->store;
199
200     $tx = $t->ua->build_tx( POST => "/api/v1/auth/otp/token_delivery" );
201     $tx->req->cookies( { name => 'CGISESSID', value => $session->id } );
202     $tx->req->env( { REMOTE_ADDR => $remote_address } );
203
204     # Invalid email
205     $t->request_ok($tx)->status_is(400)->json_is({ error => 'email_not_sent' });
206
207     $patron->email('to@example.org')->store;
208     $tx = $t->ua->build_tx( POST => "/api/v1/auth/otp/token_delivery" );
209     $tx->req->cookies( { name => 'CGISESSID', value => $session->id } );
210     $tx->req->env( { REMOTE_ADDR => $remote_address } );
211
212     # Everything is ok, the email will be sent
213     $t->request_ok($tx)->status_is(200);
214
215     # Change flags: not enough authorization anymore
216     $patron->flags(16)->store;
217     $tx = $t->ua->build_tx( POST => "/api/v1/auth/otp/token_delivery" );
218     $tx->req->cookies( { name => 'CGISESSID', value => $session->id } );
219     $tx->req->env( { REMOTE_ADDR => $remote_address } );
220     $t->request_ok($tx)->status_is(403);
221     $patron->flags(20)->store;
222
223     # Patron is fully authenticated, cannot request a token again
224     $session->param('waiting-for-2FA', 0);
225     $session->flush;
226     $tx = $t->ua->build_tx( POST => "/api/v1/auth/otp/token_delivery" );
227     $tx->req->cookies( { name => 'CGISESSID', value => $session->id } );
228     $tx->req->env( { REMOTE_ADDR => $remote_address } );
229
230     $t->request_ok($tx)->status_is(401);
231
232     $schema->storage->txn_rollback;
233 };
234
235 1;