blob: 117b690bb561d7fa1c35900c5e378dce0dd932d0 [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)
Akrone208d302020-11-28 11:14:50 +0100117 ->element_exists('form[action=/user/login] input[name=handle]')
Akron33f5c672019-06-24 19:40:47 +0200118 ->element_exists('aside.active')
119 ->element_exists_not('aside.off')
120 ;
121
Akron3e0fdc12020-05-15 16:17:21 +0200122# Test for bug with long password
123$t->post_ok('/user/login' => form => {
Akrone208d302020-11-28 11:14:50 +0100124 handle => 'test',
Akron3e0fdc12020-05-15 16:17:21 +0200125 pwd => 'kjskjhndkjndqknaskjnakjdnkjdankajdnkjdsankjdsakjdfkjahzroiuqzriudjoijdmlamdlkmdsalkmdl' })
126 ->status_is(302)
127 ->header_is('Location' => '/');
128
Akrone208d302020-11-28 11:14:50 +0100129$t->post_ok('/user/login' => form => { handle => 'test', pwd => 'fail' })
Akron33f5c672019-06-24 19:40:47 +0200130 ->status_is(302)
131 ->header_is('Location' => '/');
132
133$t->get_ok('/')
134 ->status_is(200)
135 ->element_exists('div.notify-error')
136 ->text_is('div.notify-error', 'Bad CSRF token')
Akrone208d302020-11-28 11:14:50 +0100137 ->element_exists('input[name=handle][value=test]')
Akron33f5c672019-06-24 19:40:47 +0200138 ->element_exists_not('div.button.top a')
139 ;
140
Akrone208d302020-11-28 11:14:50 +0100141$t->post_ok('/user/login' => form => { handle => 'test', pwd => 'pass' })
Akron33f5c672019-06-24 19:40:47 +0200142 ->status_is(302)
143 ->header_is('Location' => '/');
144
145my $csrf = $t->get_ok('/')
146 ->status_is(200)
147 ->element_exists('div.notify-error')
148 ->text_is('div.notify-error', 'Bad CSRF token')
149 ->element_exists_not('div.button.top a')
150 ->tx->res->dom->at('input[name=csrf_token]')->attr('value')
151 ;
152
153$t->post_ok('/user/login' => form => {
Akrone208d302020-11-28 11:14:50 +0100154 handle => 'test',
Akron33f5c672019-06-24 19:40:47 +0200155 pwd => 'ldaperr',
156 csrf_token => $csrf
157})
158 ->status_is(302)
159 ->content_is('')
160 ->header_is('Location' => '/');
161
162$csrf = $t->get_ok('/')
163 ->status_is(200)
164 ->element_exists('div.notify-error')
165 ->text_is('div.notify-error', '2022: LDAP Authentication failed due to unknown user or password!')
Akrone208d302020-11-28 11:14:50 +0100166 ->element_exists('input[name=handle][value=test]')
Akron33f5c672019-06-24 19:40:47 +0200167 ->element_exists_not('div.button.top a')
Akron3b3c7af2020-05-15 16:23:55 +0200168 ->element_exists_not('div.notify-success')
Akron33f5c672019-06-24 19:40:47 +0200169 ->tx->res->dom->at('input[name=csrf_token]')->attr('value')
170 ;
171
172$t->post_ok('/user/login' => form => {
Akrone208d302020-11-28 11:14:50 +0100173 handle => 'test',
Akron33f5c672019-06-24 19:40:47 +0200174 pwd => 'unknown',
175 csrf_token => $csrf
176})
177 ->status_is(302)
178 ->content_is('')
179 ->header_is('Location' => '/');
180
181$csrf = $t->get_ok('/')
182 ->status_is(200)
183 ->element_exists('div.notify-error')
Akron8bbbecf2019-07-01 18:57:30 +0200184 ->text_is('div.notify-error', '2022: LDAP Authentication failed due to unknown user or password!')
Akrone208d302020-11-28 11:14:50 +0100185 ->element_exists('input[name=handle][value=test]')
Akron33f5c672019-06-24 19:40:47 +0200186 ->element_exists_not('div.button.top a')
187 ->tx->res->dom->at('input[name=csrf_token]')->attr('value')
188 ;
189
190$t->post_ok('/user/login' => form => {
Akrone208d302020-11-28 11:14:50 +0100191 handle => 'test',
Akron33f5c672019-06-24 19:40:47 +0200192 pwd => 'pass',
193 csrf_token => $csrf
194})
195 ->status_is(302)
196 ->content_is('')
197 ->header_is('Location' => '/');
198
199$t->get_ok('/')
200 ->status_is(200)
201 ->element_exists_not('div.notify-error')
202 ->element_exists('div.notify-success')
203 ->text_is('div.notify-success', 'Login successful')
204 ->element_exists('aside.off')
205 ->element_exists_not('aside.active')
206 ;
207
Akron33f5c672019-06-24 19:40:47 +0200208# Now the user is logged in and should be able to
209# search with authorization
210$t->get_ok('/?q=Baum')
211 ->status_is(200)
Akron4cefe1f2019-09-04 10:11:28 +0200212 ->session_has('/auth')
213 ->session_is('/auth', 'Bearer ' . $access_token)
214 ->session_is('/auth_r', $refresh_token)
215 ->session_is('/user', 'test')
Akron33f5c672019-06-24 19:40:47 +0200216 ->text_like('h1 span', qr/KorAP: Find .Baum./i)
217 ->text_like('#total-results', qr/\d+$/)
218 ->element_exists_not('div.notify-error')
219 ->content_like(qr/\"authorized\"\:\"yes\"/)
220 ->element_exists('div.button.top a')
221 ->element_exists('div.button.top a.logout[title~="test"]')
222 ;
223
Akron27031aa2020-04-28 14:57:10 +0200224$t->get_ok('/?q=Paum')
225 ->status_is(200)
226 ->text_like('h1 span', qr/KorAP: Find .Paum./i)
227 ->text_is('#total-results', '')
228 ->content_like(qr/\"authorized\"\:\"yes\"/)
229 ->element_exists_not('p.hint')
230 ;
231
232
Akron33f5c672019-06-24 19:40:47 +0200233# Logout
234$t->get_ok('/user/logout')
235 ->status_is(302)
Akron4cefe1f2019-09-04 10:11:28 +0200236 ->session_hasnt('/auth')
237 ->session_hasnt('/auth_r')
238 ->session_hasnt('/user')
Akron33f5c672019-06-24 19:40:47 +0200239 ->header_is('Location' => '/');
240
241$t->get_ok('/')
242 ->status_is(200)
243 ->element_exists_not('div.notify-error')
244 ->element_exists('div.notify-success')
245 ->text_is('div.notify-success', 'Logout successful')
Akrone208d302020-11-28 11:14:50 +0100246 ->element_exists("input[name=handle]")
247 ->element_exists("input[name=handle][value=test]")
Akron33f5c672019-06-24 19:40:47 +0200248 ;
249
250$t->get_ok('/?q=Baum')
251 ->status_is(200)
252 ->text_like('h1 span', qr/KorAP: Find .Baum./i)
253 ->text_like('#total-results', qr/\d+$/)
254 ->content_like(qr/\"authorized\"\:null/)
255 ;
256
Akron27031aa2020-04-28 14:57:10 +0200257$t->get_ok('/?q=Paum')
258 ->status_is(200)
259 ->text_like('h1 span', qr/KorAP: Find .Paum./i)
260 ->text_is('#total-results', '')
261 ->content_like(qr/\"authorized\"\:null/)
262 ->text_is('p.hint', 'Maybe you need to log in first?')
263 ;
264
265
Akron33f5c672019-06-24 19:40:47 +0200266# Get redirect
267my $fwd = $t->get_ok('/?q=Baum&ql=poliqarp')
268 ->status_is(200)
269 ->element_exists_not('div.notify-error')
270 ->tx->res->dom->at('input[name=fwd]')->attr('value')
271 ;
272
273is($fwd, '/?q=Baum&ql=poliqarp', 'Redirect is valid');
274
275$t->post_ok('/user/login' => form => {
Akrone208d302020-11-28 11:14:50 +0100276 handle => 'test',
Akron33f5c672019-06-24 19:40:47 +0200277 pwd => 'pass',
278 csrf_token => $csrf,
279 fwd => 'http://bad.example.com/test'
280})
281 ->status_is(302)
282 ->header_is('Location' => '/');
283
284$t->get_ok('/')
285 ->status_is(200)
286 ->element_exists('div.notify-error')
287 ->element_exists_not('div.notify-success')
288 ->text_is('div.notify-error', 'Redirect failure')
289 ;
290
291$t->post_ok('/user/login' => form => {
Akrone208d302020-11-28 11:14:50 +0100292 handle => 'test',
Akron33f5c672019-06-24 19:40:47 +0200293 pwd => 'pass',
294 csrf_token => $csrf,
295 fwd => $fwd
296})
297 ->status_is(302)
298 ->header_is('Location' => '/?q=Baum&ql=poliqarp');
299
Akron8bbbecf2019-07-01 18:57:30 +0200300$t->get_ok('/?q=Baum&ql=poliqarp')
301 ->status_is(200)
302 ->element_exists_not('div.notify-error')
303 ->element_exists('div.notify-success')
304 ->text_is('div.notify-success', 'Login successful')
Akroncdfd9d52019-07-23 11:35:00 +0200305 ->session_has('/auth')
306 ->session_is('/auth', 'Bearer ' . $access_token)
307 ->session_is('/auth_r', $refresh_token)
308 ->header_isnt('X-Kalamar-Cache', 'true')
Akron8bbbecf2019-07-01 18:57:30 +0200309 ;
310
Akroncdfd9d52019-07-23 11:35:00 +0200311# Expire the session
312# (makes the token be marked as expired - though it isn't serverside)
313$t->get_ok('/x/expire')
Akron8bbbecf2019-07-01 18:57:30 +0200314 ->status_is(200)
Akroncdfd9d52019-07-23 11:35:00 +0200315 ->content_is('okay')
316 ;
317
318## It may be a problem, but the cache is still valid
319$t->get_ok('/?q=Baum')
320 ->status_is(200)
321 ->text_like('h1 span', qr/KorAP: Find .Baum./i)
322 ->text_like('#total-results', qr/\d+$/)
323 ->content_like(qr/\"authorized\"\:\"yes\"/)
324 ->header_is('X-Kalamar-Cache', 'true')
325 ;
326
327# Query without partial cache (unfortunately) (but no total results)
328$t->get_ok('/?q=baum&cutoff=true')
329 ->status_is(200)
330 ->session_is('/auth', 'Bearer ' . $access_token_2)
331 ->session_is('/auth_r', $refresh_token_2)
332 ->text_is('#error','')
333 ->text_is('title', 'KorAP: Find »baum« with Poliqarp')
334 ->element_exists('meta[name="DC.title"][content="KorAP: Find »baum« with Poliqarp"]')
335 ->element_exists('body[itemscope][itemtype="http://schema.org/SearchResultsPage"]')
336 ->content_like(qr/\"authorized\"\:\"yes\"/)
337 ->header_isnt('X-Kalamar-Cache', 'true')
338 ->content_like(qr!\"cutOff":true!)
339 ->element_exists_not('#total-results')
340 ;
341
342# Expire the session and remove the refresh option
343$t->get_ok('/x/expire-no-refresh')
344 ->status_is(200)
345 ->content_is('okay')
346 ;
347
348$t->app->defaults(no_cache => 1);
349
350
351$t->get_ok('/x/invalid-no-refresh')
352 ->status_is(200)
353 ->content_is('okay')
354 ;
355
356# Query without cache
357# The token is invalid and can't be refreshed!
358$t->get_ok('/?q=baum&cutoff=true')
Akron3c390c42020-03-30 09:06:21 +0200359 ->status_is(400)
Akroncdfd9d52019-07-23 11:35:00 +0200360 ->session_hasnt('/auth')
361 ->session_hasnt('/auth_r')
362 ->text_is('#error','')
363 ->text_is('div.notify-error','Access token invalid')
364 ->text_is('title', 'KorAP: Find »baum« with Poliqarp')
365 ->element_exists('meta[name="DC.title"][content="KorAP: Find »baum« with Poliqarp"]')
366 ->element_exists('body[itemscope][itemtype="http://schema.org/SearchResultsPage"]')
367 ->content_unlike(qr/\"authorized\"\:\"yes\"/)
368 ->header_isnt('X-Kalamar-Cache', 'true')
369 ->element_exists('p.no-results')
370 ;
371
372$t->get_ok('/x/invalid')
373 ->status_is(200)
374 ->content_is('okay')
375 ;
376
377# Query without cache
378# The token is invalid and can't be refreshed!
379$t->get_ok('/?q=baum&cutoff=true')
380 ->status_is(200)
381 ->session_is('/auth', 'Bearer ' . $access_token_2)
382 ->session_is('/auth_r', $refresh_token_2)
383 ->text_is('#error','')
384 ->element_exists_not('div.notify-error','Access token invalid')
385 ->text_is('title', 'KorAP: Find »baum« with Poliqarp')
386 ->element_exists('meta[name="DC.title"][content="KorAP: Find »baum« with Poliqarp"]')
387 ->element_exists('body[itemscope][itemtype="http://schema.org/SearchResultsPage"]')
388 ->content_like(qr/\"authorized\"\:\"yes\"/)
389 ->header_isnt('X-Kalamar-Cache', 'true')
390 ->element_exists_not('p.no-results')
Akron8bbbecf2019-07-01 18:57:30 +0200391 ;
392
Akron33f5c672019-06-24 19:40:47 +0200393
Akroncdfd9d52019-07-23 11:35:00 +0200394$t->get_ok('/x/expired-with-wrong-refresh')
395 ->status_is(200)
396 ->content_is('okay')
397 ;
Akron4796e002019-07-05 10:13:15 +0200398
Akron4796e002019-07-05 10:13:15 +0200399
Akroncdfd9d52019-07-23 11:35:00 +0200400# The token is invalid and can't be refreshed!
Akron59992122019-10-29 11:28:45 +0100401$csrf = $t->get_ok('/?q=baum&cutoff=true')
Akron3c390c42020-03-30 09:06:21 +0200402 ->status_is(400)
Akroncdfd9d52019-07-23 11:35:00 +0200403 ->session_hasnt('/auth')
404 ->session_hasnt('/auth_r')
405 ->text_is('#error','')
406 ->text_is('div.notify-error','Refresh token is expired')
407 ->text_is('title', 'KorAP: Find »baum« with Poliqarp')
408 ->content_unlike(qr/\"authorized\"\:\"yes\"/)
409 ->element_exists('p.no-results')
Akron59992122019-10-29 11:28:45 +0100410 ->tx->res->dom->at('input[name="csrf_token"]')
411 ->attr('value')
Akroncdfd9d52019-07-23 11:35:00 +0200412 ;
Akron4796e002019-07-05 10:13:15 +0200413
Akron59992122019-10-29 11:28:45 +0100414# Login:
415$t->post_ok('/user/login' => form => {
Akrone208d302020-11-28 11:14:50 +0100416 handle => 'test',
Akron59992122019-10-29 11:28:45 +0100417 pwd => 'pass',
418 csrf_token => $csrf
419})
420 ->status_is(302)
421 ->content_is('')
422 ->header_is('Location' => '/');
423
424$t->get_ok('/')
425 ->status_is(200)
426 ->element_exists_not('div.notify-error')
427 ->element_exists('div.notify-success')
428 ->text_is('div.notify-success', 'Login successful')
429 ->element_exists('aside.off')
430 ->element_exists_not('aside.active')
431 ;
432
433$t->get_ok('/settings/oauth')
434 ->text_is('form.form-table legend', 'Register new client application')
435 ->attr_is('form.oauth-register','action', '/settings/oauth/register')
Akron1a9d5be2020-03-19 17:28:33 +0100436 ->element_exists('ul.client-list')
437 ->element_exists_not('ul.client-list > li')
438# ->text_is('ul.client-list > li > span.client-name', 'R statistical computing tool ')
439# ->text_is('ul.client-list > li > span.client-desc', 'R is a free software environment for statistical computing and graphics.')
440# ->text_is('ul.client-list > li > span.client-url a', 'https://www.r-project.org/')
441# ->text_is('ul.client-list > li a.client-unregister', 'Unregister')
442# ->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 +0100443 ;
Akron59992122019-10-29 11:28:45 +0100444$csrf = $t->post_ok('/settings/oauth/register' => form => {
445 name => 'MyApp',
446 type => 'PUBLIC',
447 desc => 'This is my application'
448})
449 ->text_is('div.notify-error', 'Bad CSRF token')
450 ->tx->res->dom->at('input[name="csrf_token"]')
451 ->attr('value')
452 ;
453
454$t->post_ok('/settings/oauth/register' => form => {
455 name => 'MyApp',
456 type => 'CONFIDENTIAL',
457 desc => 'This is my application',
458 csrf_token => $csrf
459})
460 ->status_is(200)
461 ->element_exists('div.notify-success')
462 ->text_is('legend', 'Client Credentials')
463 ->text_is('label[for=client_id]', 'ID of the client application')
464 ->element_exists('input[name=client_id][readonly][value]')
465 ->element_exists('input[name=client_secret][readonly][value]')
466 ;
Akron4796e002019-07-05 10:13:15 +0200467
Akron1a9d5be2020-03-19 17:28:33 +0100468$t->get_ok('/settings/oauth')
469 ->text_is('form.form-table legend', 'Register new client application')
470 ->attr_is('form.oauth-register','action', '/settings/oauth/register')
Akron17de86e2020-04-16 16:03:40 +0200471 ->text_is('ul.client-list > li > span.client-name a', 'MyApp')
Akron1a9d5be2020-03-19 17:28:33 +0100472 ->text_is('ul.client-list > li > span.client-desc', 'This is my application')
473 ->text_is('ul.client-list > li > span.client-url a', '')
Akron17de86e2020-04-16 16:03:40 +0200474 ;
475
476$t->get_ok('/settings/oauth/client/fCBbQkA2NDA3MzM1Yw==')
477 ->status_is(200)
478 ->text_is('form ul.client-list > li.client > span.client-name', 'MyApp')
479 ->text_is('form ul.client-list > li.client > span.client-desc', 'This is my application')
480 ->text_is('a.client-unregister', 'Unregister')
481 ->attr_is('a.client-unregister', 'href', '/settings/oauth/unregister/fCBbQkA2NDA3MzM1Yw==?name=MyApp')
Akron1a9d5be2020-03-19 17:28:33 +0100482 ;
483
484$csrf = $t->get_ok('/settings/oauth/unregister/fCBbQkA2NDA3MzM1Yw==?name=MyApp')
485 ->content_like(qr!Do you really want to unregister \<span class="client-name"\>MyApp\<\/span\>?!)
486 ->attr_is('form.form-table input[name=client-id]', 'value', 'fCBbQkA2NDA3MzM1Yw==')
487 ->attr_is('form.form-table input[name=client-name]', 'value', 'MyApp')
488 ->tx->res->dom->at('input[name="csrf_token"]')
489 ->attr('value')
490 ;
491
492$t->post_ok('/settings/oauth/unregister' => form => {
493 'client-name' => 'MyApp',
494 'client-id' => 'xxxx==',
495 'csrf_token' => $csrf
496})->status_is(302)
497 ->content_is('')
498 ->header_is('Location' => '/settings/oauth')
499 ;
500
501$t->get_ok('/settings/oauth')
502 ->text_is('form.form-table legend', 'Register new client application')
503 ->attr_is('form.oauth-register','action', '/settings/oauth/register')
504 ->element_exists('ul.client-list > li')
505 ->text_is('div.notify', 'Unknown client with xxxx==.')
506 ;
507
508$t->post_ok('/settings/oauth/unregister' => form => {
509 'client-name' => 'MyApp',
510 'client-id' => 'fCBbQkA2NDA3MzM1Yw==',
511 'csrf_token' => $csrf
512})->status_is(302)
513 ->content_is('')
514 ->header_is('Location' => '/settings/oauth')
515 ;
516
517$t->get_ok('/settings/oauth')
518 ->text_is('form.form-table legend', 'Register new client application')
519 ->attr_is('form.oauth-register','action', '/settings/oauth/register')
520 ->element_exists_not('ul.client-list > li')
521 ->text_is('div.notify-success', 'Successfully deleted MyApp')
522 ;
523
Akron33f5c672019-06-24 19:40:47 +0200524done_testing;
525__END__