blob: f8ad55fb059015eb6a32e5fefc09ac0aefc98eb9 [file] [log] [blame]
Akron33f5c672019-06-24 19:40:47 +02001use Mojo::Base -strict;
2use Test::More;
Akroncdfd9d52019-07-23 11:35:00 +02003use Test::Mojo::WithRoles 'Session';
Akron33f5c672019-06-24 19:40:47 +02004use Mojo::File qw/path/;
5use Data::Dumper;
6
7
8#####################
9# Start Fake server #
10#####################
Akron63d963b2019-07-05 15:35:51 +020011my $mount_point = '/realapi/';
Akron33f5c672019-06-24 19:40:47 +020012$ENV{KALAMAR_API} = $mount_point;
13
Akroncdfd9d52019-07-23 11:35:00 +020014my $t = Test::Mojo::WithRoles->new('Kalamar' => {
Akron33f5c672019-06-24 19:40:47 +020015 Kalamar => {
16 plugins => ['Auth']
17 },
18 'Kalamar-Auth' => {
19 client_id => 2,
20 client_secret => 'k414m4r-s3cr3t',
Akron59992122019-10-29 11:28:45 +010021 oauth2 => 1,
22 experimental_client_registration => 1
Akron33f5c672019-06-24 19:40:47 +020023 }
24});
25
26# Mount fake backend
27# Get the fixture path
28my $fixtures_path = path(Mojo::File->new(__FILE__)->dirname, '..', 'server');
29my $fake_backend = $t->app->plugin(
30 Mount => {
31 $mount_point =>
32 $fixtures_path->child('mock.pl')
33 }
34);
35# Configure fake backend
Akroncdfd9d52019-07-23 11:35:00 +020036my $fake_backend_app = $fake_backend->pattern->defaults->{app};
37
38# Set general app logger for simplicity
39$fake_backend_app->log($t->app->log);
40
41my $access_token = $fake_backend_app->get_token('access_token');
42my $refresh_token = $fake_backend_app->get_token('refresh_token');
43my $access_token_2 = $fake_backend_app->get_token('access_token_2');
44my $refresh_token_2 = $fake_backend_app->get_token('refresh_token_2');
45
46# Some routes to modify the session
47# This expires the session
48$t->app->routes->get('/x/expire')->to(
49 cb => sub {
50 my $c = shift;
51 $c->session(auth_exp => 0);
52 return $c->render(text => 'okay')
53 }
54);
55
56# This expires the session and removes the refresh token
57$t->app->routes->get('/x/expire-no-refresh')->to(
58 cb => sub {
59 my $c = shift;
60 $c->session(auth_exp => 0);
61 delete $c->session->{auth_r};
62 return $c->render(text => 'okay')
63 }
64);
65
66# This sets an invalid token
67$t->app->routes->get('/x/invalid')->to(
68 cb => sub {
69 my $c = shift;
70 $c->session(auth_exp => time + 1000);
71 $c->session(auth_r => $refresh_token_2);
72 $c->session(auth => 'Bearer inv4lid');
73 return $c->render(text => 'okay')
74 }
75);
76
77
78# This sets an invalid token
79$t->app->routes->get('/x/invalid-no-refresh')->to(
80 cb => sub {
81 my $c = shift;
82 $c->session(auth_exp => time + 1000);
83 delete $c->session->{auth_r};
84 $c->session(auth => 'Bearer inv4lid');
85 return $c->render(text => 'okay')
86 }
87);
88
89# This sets an invalid refresh token
90$t->app->routes->get('/x/expired-with-wrong-refresh')->to(
91 cb => sub {
92 my $c = shift;
93 $c->session(auth_exp => 0);
94 $c->session(auth => 'Bearer inv4lid');
95 $c->session(auth_r => 'inv4lid');
96 return $c->render(text => 'okay')
97 }
98);
99
Akron33f5c672019-06-24 19:40:47 +0200100
Akron63d963b2019-07-05 15:35:51 +0200101$t->get_ok('/realapi/v1.0')
Akron33f5c672019-06-24 19:40:47 +0200102 ->status_is(200)
103 ->content_is('Fake server available');
104
105$t->get_ok('/?q=Baum')
106 ->status_is(200)
107 ->text_like('h1 span', qr/KorAP: Find .Baum./i)
108 ->text_like('#total-results', qr/\d+$/)
109 ->content_like(qr/\"authorized\"\:null/)
110 ->element_exists_not('div.button.top a')
111 ->element_exists_not('aside.active')
112 ->element_exists_not('aside.off')
113 ;
114
115$t->get_ok('/')
116 ->status_is(200)
117 ->element_exists('form[action=/user/login] input[name=handle_or_email]')
118 ->element_exists('aside.active')
119 ->element_exists_not('aside.off')
120 ;
121
122$t->post_ok('/user/login' => form => { handle_or_email => 'test', pwd => 'fail' })
123 ->status_is(302)
124 ->header_is('Location' => '/');
125
126$t->get_ok('/')
127 ->status_is(200)
128 ->element_exists('div.notify-error')
129 ->text_is('div.notify-error', 'Bad CSRF token')
130 ->element_exists('input[name=handle_or_email][value=test]')
131 ->element_exists_not('div.button.top a')
132 ;
133
134$t->post_ok('/user/login' => form => { handle_or_email => 'test', pwd => 'pass' })
135 ->status_is(302)
136 ->header_is('Location' => '/');
137
138my $csrf = $t->get_ok('/')
139 ->status_is(200)
140 ->element_exists('div.notify-error')
141 ->text_is('div.notify-error', 'Bad CSRF token')
142 ->element_exists_not('div.button.top a')
143 ->tx->res->dom->at('input[name=csrf_token]')->attr('value')
144 ;
145
146$t->post_ok('/user/login' => form => {
147 handle_or_email => 'test',
148 pwd => 'ldaperr',
149 csrf_token => $csrf
150})
151 ->status_is(302)
152 ->content_is('')
153 ->header_is('Location' => '/');
154
155$csrf = $t->get_ok('/')
156 ->status_is(200)
157 ->element_exists('div.notify-error')
158 ->text_is('div.notify-error', '2022: LDAP Authentication failed due to unknown user or password!')
159 ->element_exists('input[name=handle_or_email][value=test]')
160 ->element_exists_not('div.button.top a')
Akron3b3c7af2020-05-15 16:23:55 +0200161 ->element_exists_not('div.notify-success')
Akron33f5c672019-06-24 19:40:47 +0200162 ->tx->res->dom->at('input[name=csrf_token]')->attr('value')
163 ;
164
165$t->post_ok('/user/login' => form => {
166 handle_or_email => 'test',
167 pwd => 'unknown',
168 csrf_token => $csrf
169})
170 ->status_is(302)
171 ->content_is('')
172 ->header_is('Location' => '/');
173
174$csrf = $t->get_ok('/')
175 ->status_is(200)
176 ->element_exists('div.notify-error')
Akron8bbbecf2019-07-01 18:57:30 +0200177 ->text_is('div.notify-error', '2022: LDAP Authentication failed due to unknown user or password!')
Akron33f5c672019-06-24 19:40:47 +0200178 ->element_exists('input[name=handle_or_email][value=test]')
179 ->element_exists_not('div.button.top a')
180 ->tx->res->dom->at('input[name=csrf_token]')->attr('value')
181 ;
182
183$t->post_ok('/user/login' => form => {
184 handle_or_email => 'test',
185 pwd => 'pass',
186 csrf_token => $csrf
187})
188 ->status_is(302)
189 ->content_is('')
190 ->header_is('Location' => '/');
191
192$t->get_ok('/')
193 ->status_is(200)
194 ->element_exists_not('div.notify-error')
195 ->element_exists('div.notify-success')
196 ->text_is('div.notify-success', 'Login successful')
197 ->element_exists('aside.off')
198 ->element_exists_not('aside.active')
199 ;
200
Akron33f5c672019-06-24 19:40:47 +0200201# Now the user is logged in and should be able to
202# search with authorization
203$t->get_ok('/?q=Baum')
204 ->status_is(200)
Akron4cefe1f2019-09-04 10:11:28 +0200205 ->session_has('/auth')
206 ->session_is('/auth', 'Bearer ' . $access_token)
207 ->session_is('/auth_r', $refresh_token)
208 ->session_is('/user', 'test')
Akron33f5c672019-06-24 19:40:47 +0200209 ->text_like('h1 span', qr/KorAP: Find .Baum./i)
210 ->text_like('#total-results', qr/\d+$/)
211 ->element_exists_not('div.notify-error')
212 ->content_like(qr/\"authorized\"\:\"yes\"/)
213 ->element_exists('div.button.top a')
214 ->element_exists('div.button.top a.logout[title~="test"]')
215 ;
216
Akron27031aa2020-04-28 14:57:10 +0200217$t->get_ok('/?q=Paum')
218 ->status_is(200)
219 ->text_like('h1 span', qr/KorAP: Find .Paum./i)
220 ->text_is('#total-results', '')
221 ->content_like(qr/\"authorized\"\:\"yes\"/)
222 ->element_exists_not('p.hint')
223 ;
224
225
Akron33f5c672019-06-24 19:40:47 +0200226# Logout
227$t->get_ok('/user/logout')
228 ->status_is(302)
Akron4cefe1f2019-09-04 10:11:28 +0200229 ->session_hasnt('/auth')
230 ->session_hasnt('/auth_r')
231 ->session_hasnt('/user')
Akron33f5c672019-06-24 19:40:47 +0200232 ->header_is('Location' => '/');
233
234$t->get_ok('/')
235 ->status_is(200)
236 ->element_exists_not('div.notify-error')
237 ->element_exists('div.notify-success')
238 ->text_is('div.notify-success', 'Logout successful')
Akron4cefe1f2019-09-04 10:11:28 +0200239 ->element_exists("input[name=handle_or_email]")
240 ->element_exists("input[name=handle_or_email][value=test]")
Akron33f5c672019-06-24 19:40:47 +0200241 ;
242
243$t->get_ok('/?q=Baum')
244 ->status_is(200)
245 ->text_like('h1 span', qr/KorAP: Find .Baum./i)
246 ->text_like('#total-results', qr/\d+$/)
247 ->content_like(qr/\"authorized\"\:null/)
248 ;
249
Akron27031aa2020-04-28 14:57:10 +0200250$t->get_ok('/?q=Paum')
251 ->status_is(200)
252 ->text_like('h1 span', qr/KorAP: Find .Paum./i)
253 ->text_is('#total-results', '')
254 ->content_like(qr/\"authorized\"\:null/)
255 ->text_is('p.hint', 'Maybe you need to log in first?')
256 ;
257
258
Akron33f5c672019-06-24 19:40:47 +0200259# Get redirect
260my $fwd = $t->get_ok('/?q=Baum&ql=poliqarp')
261 ->status_is(200)
262 ->element_exists_not('div.notify-error')
263 ->tx->res->dom->at('input[name=fwd]')->attr('value')
264 ;
265
266is($fwd, '/?q=Baum&ql=poliqarp', 'Redirect is valid');
267
268$t->post_ok('/user/login' => form => {
269 handle_or_email => 'test',
270 pwd => 'pass',
271 csrf_token => $csrf,
272 fwd => 'http://bad.example.com/test'
273})
274 ->status_is(302)
275 ->header_is('Location' => '/');
276
277$t->get_ok('/')
278 ->status_is(200)
279 ->element_exists('div.notify-error')
280 ->element_exists_not('div.notify-success')
281 ->text_is('div.notify-error', 'Redirect failure')
282 ;
283
284$t->post_ok('/user/login' => form => {
285 handle_or_email => 'test',
286 pwd => 'pass',
287 csrf_token => $csrf,
288 fwd => $fwd
289})
290 ->status_is(302)
291 ->header_is('Location' => '/?q=Baum&ql=poliqarp');
292
Akron8bbbecf2019-07-01 18:57:30 +0200293$t->get_ok('/?q=Baum&ql=poliqarp')
294 ->status_is(200)
295 ->element_exists_not('div.notify-error')
296 ->element_exists('div.notify-success')
297 ->text_is('div.notify-success', 'Login successful')
Akroncdfd9d52019-07-23 11:35:00 +0200298 ->session_has('/auth')
299 ->session_is('/auth', 'Bearer ' . $access_token)
300 ->session_is('/auth_r', $refresh_token)
301 ->header_isnt('X-Kalamar-Cache', 'true')
Akron8bbbecf2019-07-01 18:57:30 +0200302 ;
303
Akroncdfd9d52019-07-23 11:35:00 +0200304# Expire the session
305# (makes the token be marked as expired - though it isn't serverside)
306$t->get_ok('/x/expire')
Akron8bbbecf2019-07-01 18:57:30 +0200307 ->status_is(200)
Akroncdfd9d52019-07-23 11:35:00 +0200308 ->content_is('okay')
309 ;
310
311## It may be a problem, but the cache is still valid
312$t->get_ok('/?q=Baum')
313 ->status_is(200)
314 ->text_like('h1 span', qr/KorAP: Find .Baum./i)
315 ->text_like('#total-results', qr/\d+$/)
316 ->content_like(qr/\"authorized\"\:\"yes\"/)
317 ->header_is('X-Kalamar-Cache', 'true')
318 ;
319
320# Query without partial cache (unfortunately) (but no total results)
321$t->get_ok('/?q=baum&cutoff=true')
322 ->status_is(200)
323 ->session_is('/auth', 'Bearer ' . $access_token_2)
324 ->session_is('/auth_r', $refresh_token_2)
325 ->text_is('#error','')
326 ->text_is('title', 'KorAP: Find »baum« with Poliqarp')
327 ->element_exists('meta[name="DC.title"][content="KorAP: Find »baum« with Poliqarp"]')
328 ->element_exists('body[itemscope][itemtype="http://schema.org/SearchResultsPage"]')
329 ->content_like(qr/\"authorized\"\:\"yes\"/)
330 ->header_isnt('X-Kalamar-Cache', 'true')
331 ->content_like(qr!\"cutOff":true!)
332 ->element_exists_not('#total-results')
333 ;
334
335# Expire the session and remove the refresh option
336$t->get_ok('/x/expire-no-refresh')
337 ->status_is(200)
338 ->content_is('okay')
339 ;
340
341$t->app->defaults(no_cache => 1);
342
343
344$t->get_ok('/x/invalid-no-refresh')
345 ->status_is(200)
346 ->content_is('okay')
347 ;
348
349# Query without cache
350# The token is invalid and can't be refreshed!
351$t->get_ok('/?q=baum&cutoff=true')
Akron3c390c42020-03-30 09:06:21 +0200352 ->status_is(400)
Akroncdfd9d52019-07-23 11:35:00 +0200353 ->session_hasnt('/auth')
354 ->session_hasnt('/auth_r')
355 ->text_is('#error','')
356 ->text_is('div.notify-error','Access token invalid')
357 ->text_is('title', 'KorAP: Find »baum« with Poliqarp')
358 ->element_exists('meta[name="DC.title"][content="KorAP: Find »baum« with Poliqarp"]')
359 ->element_exists('body[itemscope][itemtype="http://schema.org/SearchResultsPage"]')
360 ->content_unlike(qr/\"authorized\"\:\"yes\"/)
361 ->header_isnt('X-Kalamar-Cache', 'true')
362 ->element_exists('p.no-results')
363 ;
364
365$t->get_ok('/x/invalid')
366 ->status_is(200)
367 ->content_is('okay')
368 ;
369
370# Query without cache
371# The token is invalid and can't be refreshed!
372$t->get_ok('/?q=baum&cutoff=true')
373 ->status_is(200)
374 ->session_is('/auth', 'Bearer ' . $access_token_2)
375 ->session_is('/auth_r', $refresh_token_2)
376 ->text_is('#error','')
377 ->element_exists_not('div.notify-error','Access token invalid')
378 ->text_is('title', 'KorAP: Find »baum« with Poliqarp')
379 ->element_exists('meta[name="DC.title"][content="KorAP: Find »baum« with Poliqarp"]')
380 ->element_exists('body[itemscope][itemtype="http://schema.org/SearchResultsPage"]')
381 ->content_like(qr/\"authorized\"\:\"yes\"/)
382 ->header_isnt('X-Kalamar-Cache', 'true')
383 ->element_exists_not('p.no-results')
Akron8bbbecf2019-07-01 18:57:30 +0200384 ;
385
Akron33f5c672019-06-24 19:40:47 +0200386
Akroncdfd9d52019-07-23 11:35:00 +0200387$t->get_ok('/x/expired-with-wrong-refresh')
388 ->status_is(200)
389 ->content_is('okay')
390 ;
Akron4796e002019-07-05 10:13:15 +0200391
Akron4796e002019-07-05 10:13:15 +0200392
Akroncdfd9d52019-07-23 11:35:00 +0200393# The token is invalid and can't be refreshed!
Akron59992122019-10-29 11:28:45 +0100394$csrf = $t->get_ok('/?q=baum&cutoff=true')
Akron3c390c42020-03-30 09:06:21 +0200395 ->status_is(400)
Akroncdfd9d52019-07-23 11:35:00 +0200396 ->session_hasnt('/auth')
397 ->session_hasnt('/auth_r')
398 ->text_is('#error','')
399 ->text_is('div.notify-error','Refresh token is expired')
400 ->text_is('title', 'KorAP: Find »baum« with Poliqarp')
401 ->content_unlike(qr/\"authorized\"\:\"yes\"/)
402 ->element_exists('p.no-results')
Akron59992122019-10-29 11:28:45 +0100403 ->tx->res->dom->at('input[name="csrf_token"]')
404 ->attr('value')
Akroncdfd9d52019-07-23 11:35:00 +0200405 ;
Akron4796e002019-07-05 10:13:15 +0200406
Akron59992122019-10-29 11:28:45 +0100407# Login:
408$t->post_ok('/user/login' => form => {
409 handle_or_email => 'test',
410 pwd => 'pass',
411 csrf_token => $csrf
412})
413 ->status_is(302)
414 ->content_is('')
415 ->header_is('Location' => '/');
416
417$t->get_ok('/')
418 ->status_is(200)
419 ->element_exists_not('div.notify-error')
420 ->element_exists('div.notify-success')
421 ->text_is('div.notify-success', 'Login successful')
422 ->element_exists('aside.off')
423 ->element_exists_not('aside.active')
424 ;
425
426$t->get_ok('/settings/oauth')
427 ->text_is('form.form-table legend', 'Register new client application')
428 ->attr_is('form.oauth-register','action', '/settings/oauth/register')
Akron1a9d5be2020-03-19 17:28:33 +0100429 ->element_exists('ul.client-list')
430 ->element_exists_not('ul.client-list > li')
431# ->text_is('ul.client-list > li > span.client-name', 'R statistical computing tool ')
432# ->text_is('ul.client-list > li > span.client-desc', 'R is a free software environment for statistical computing and graphics.')
433# ->text_is('ul.client-list > li > span.client-url a', 'https://www.r-project.org/')
434# ->text_is('ul.client-list > li a.client-unregister', 'Unregister')
435# ->attr_is('ul.client-list > li a.client-unregister', 'href', '/settings/oauth/unregister/9aHsGW6QflV13ixNpez?name=R+statistical+computing+tool')
Akron59992122019-10-29 11:28:45 +0100436 ;
Akron59992122019-10-29 11:28:45 +0100437$csrf = $t->post_ok('/settings/oauth/register' => form => {
438 name => 'MyApp',
439 type => 'PUBLIC',
440 desc => 'This is my application'
441})
442 ->text_is('div.notify-error', 'Bad CSRF token')
443 ->tx->res->dom->at('input[name="csrf_token"]')
444 ->attr('value')
445 ;
446
447$t->post_ok('/settings/oauth/register' => form => {
448 name => 'MyApp',
449 type => 'CONFIDENTIAL',
450 desc => 'This is my application',
451 csrf_token => $csrf
452})
453 ->status_is(200)
454 ->element_exists('div.notify-success')
455 ->text_is('legend', 'Client Credentials')
456 ->text_is('label[for=client_id]', 'ID of the client application')
457 ->element_exists('input[name=client_id][readonly][value]')
458 ->element_exists('input[name=client_secret][readonly][value]')
459 ;
Akron4796e002019-07-05 10:13:15 +0200460
Akron1a9d5be2020-03-19 17:28:33 +0100461$t->get_ok('/settings/oauth')
462 ->text_is('form.form-table legend', 'Register new client application')
463 ->attr_is('form.oauth-register','action', '/settings/oauth/register')
Akron17de86e2020-04-16 16:03:40 +0200464 ->text_is('ul.client-list > li > span.client-name a', 'MyApp')
Akron1a9d5be2020-03-19 17:28:33 +0100465 ->text_is('ul.client-list > li > span.client-desc', 'This is my application')
466 ->text_is('ul.client-list > li > span.client-url a', '')
Akron17de86e2020-04-16 16:03:40 +0200467 ;
468
469$t->get_ok('/settings/oauth/client/fCBbQkA2NDA3MzM1Yw==')
470 ->status_is(200)
471 ->text_is('form ul.client-list > li.client > span.client-name', 'MyApp')
472 ->text_is('form ul.client-list > li.client > span.client-desc', 'This is my application')
473 ->text_is('a.client-unregister', 'Unregister')
474 ->attr_is('a.client-unregister', 'href', '/settings/oauth/unregister/fCBbQkA2NDA3MzM1Yw==?name=MyApp')
Akron1a9d5be2020-03-19 17:28:33 +0100475 ;
476
477$csrf = $t->get_ok('/settings/oauth/unregister/fCBbQkA2NDA3MzM1Yw==?name=MyApp')
478 ->content_like(qr!Do you really want to unregister \<span class="client-name"\>MyApp\<\/span\>?!)
479 ->attr_is('form.form-table input[name=client-id]', 'value', 'fCBbQkA2NDA3MzM1Yw==')
480 ->attr_is('form.form-table input[name=client-name]', 'value', 'MyApp')
481 ->tx->res->dom->at('input[name="csrf_token"]')
482 ->attr('value')
483 ;
484
485$t->post_ok('/settings/oauth/unregister' => form => {
486 'client-name' => 'MyApp',
487 'client-id' => 'xxxx==',
488 'csrf_token' => $csrf
489})->status_is(302)
490 ->content_is('')
491 ->header_is('Location' => '/settings/oauth')
492 ;
493
494$t->get_ok('/settings/oauth')
495 ->text_is('form.form-table legend', 'Register new client application')
496 ->attr_is('form.oauth-register','action', '/settings/oauth/register')
497 ->element_exists('ul.client-list > li')
498 ->text_is('div.notify', 'Unknown client with xxxx==.')
499 ;
500
501$t->post_ok('/settings/oauth/unregister' => form => {
502 'client-name' => 'MyApp',
503 'client-id' => 'fCBbQkA2NDA3MzM1Yw==',
504 'csrf_token' => $csrf
505})->status_is(302)
506 ->content_is('')
507 ->header_is('Location' => '/settings/oauth')
508 ;
509
510$t->get_ok('/settings/oauth')
511 ->text_is('form.form-table legend', 'Register new client application')
512 ->attr_is('form.oauth-register','action', '/settings/oauth/register')
513 ->element_exists_not('ul.client-list > li')
514 ->text_is('div.notify-success', 'Successfully deleted MyApp')
515 ;
516
Akron33f5c672019-06-24 19:40:47 +0200517done_testing;
518__END__