blob: e12c394ba982862ed5d205d20a604d898bc55a76 [file] [log] [blame]
Akron33f5c672019-06-24 19:40:47 +02001use Mojo::Base -strict;
2use Test::More;
Akron6a228db2021-10-14 15:57:00 +02003use Mojo::ByteStream 'b';
Akroncdfd9d52019-07-23 11:35:00 +02004use Test::Mojo::WithRoles 'Session';
Akron33f5c672019-06-24 19:40:47 +02005use Mojo::File qw/path/;
6use Data::Dumper;
7
8
9#####################
10# Start Fake server #
11#####################
Akron63d963b2019-07-05 15:35:51 +020012my $mount_point = '/realapi/';
Akron33f5c672019-06-24 19:40:47 +020013$ENV{KALAMAR_API} = $mount_point;
14
Akroncdfd9d52019-07-23 11:35:00 +020015my $t = Test::Mojo::WithRoles->new('Kalamar' => {
Akron33f5c672019-06-24 19:40:47 +020016 Kalamar => {
17 plugins => ['Auth']
18 },
19 'Kalamar-Auth' => {
20 client_id => 2,
21 client_secret => 'k414m4r-s3cr3t',
Akron59992122019-10-29 11:28:45 +010022 oauth2 => 1,
23 experimental_client_registration => 1
Akron33f5c672019-06-24 19:40:47 +020024 }
25});
26
27# Mount fake backend
28# Get the fixture path
29my $fixtures_path = path(Mojo::File->new(__FILE__)->dirname, '..', 'server');
30my $fake_backend = $t->app->plugin(
31 Mount => {
32 $mount_point =>
33 $fixtures_path->child('mock.pl')
34 }
35);
36# Configure fake backend
Akroncdfd9d52019-07-23 11:35:00 +020037my $fake_backend_app = $fake_backend->pattern->defaults->{app};
38
39# Set general app logger for simplicity
40$fake_backend_app->log($t->app->log);
41
42my $access_token = $fake_backend_app->get_token('access_token');
43my $refresh_token = $fake_backend_app->get_token('refresh_token');
44my $access_token_2 = $fake_backend_app->get_token('access_token_2');
45my $refresh_token_2 = $fake_backend_app->get_token('refresh_token_2');
46
47# Some routes to modify the session
48# This expires the session
49$t->app->routes->get('/x/expire')->to(
50 cb => sub {
51 my $c = shift;
52 $c->session(auth_exp => 0);
53 return $c->render(text => 'okay')
54 }
55);
56
57# This expires the session and removes the refresh token
58$t->app->routes->get('/x/expire-no-refresh')->to(
59 cb => sub {
60 my $c = shift;
61 $c->session(auth_exp => 0);
62 delete $c->session->{auth_r};
63 return $c->render(text => 'okay')
64 }
65);
66
67# This sets an invalid token
68$t->app->routes->get('/x/invalid')->to(
69 cb => sub {
70 my $c = shift;
71 $c->session(auth_exp => time + 1000);
72 $c->session(auth_r => $refresh_token_2);
73 $c->session(auth => 'Bearer inv4lid');
74 return $c->render(text => 'okay')
75 }
76);
77
78
79# This sets an invalid token
80$t->app->routes->get('/x/invalid-no-refresh')->to(
81 cb => sub {
82 my $c = shift;
83 $c->session(auth_exp => time + 1000);
84 delete $c->session->{auth_r};
85 $c->session(auth => 'Bearer inv4lid');
86 return $c->render(text => 'okay')
87 }
88);
89
90# This sets an invalid refresh token
91$t->app->routes->get('/x/expired-with-wrong-refresh')->to(
92 cb => sub {
93 my $c = shift;
94 $c->session(auth_exp => 0);
95 $c->session(auth => 'Bearer inv4lid');
96 $c->session(auth_r => 'inv4lid');
97 return $c->render(text => 'okay')
98 }
99);
100
Akronbc6b3f22021-01-13 14:53:12 +0100101my $q = qr!(?:\"|")!;
Akron33f5c672019-06-24 19:40:47 +0200102
Akron63d963b2019-07-05 15:35:51 +0200103$t->get_ok('/realapi/v1.0')
Akron33f5c672019-06-24 19:40:47 +0200104 ->status_is(200)
105 ->content_is('Fake server available');
106
107$t->get_ok('/?q=Baum')
108 ->status_is(200)
109 ->text_like('h1 span', qr/KorAP: Find .Baum./i)
110 ->text_like('#total-results', qr/\d+$/)
Akronbc6b3f22021-01-13 14:53:12 +0100111 ->content_like(qr/${q}authorized${q}:null/)
Akron33f5c672019-06-24 19:40:47 +0200112 ->element_exists_not('div.button.top a')
113 ->element_exists_not('aside.active')
114 ->element_exists_not('aside.off')
115 ;
116
117$t->get_ok('/')
118 ->status_is(200)
Akrone208d302020-11-28 11:14:50 +0100119 ->element_exists('form[action=/user/login] input[name=handle]')
Akron33f5c672019-06-24 19:40:47 +0200120 ->element_exists('aside.active')
121 ->element_exists_not('aside.off')
122 ;
123
Akronff088112021-06-15 15:26:04 +0200124$t->get_ok('/settings/oauth')
125 ->status_is(401)
126 ->text_is('p.no-results', 'Not authenticated')
127 ;
128
Akron3e0fdc12020-05-15 16:17:21 +0200129# Test for bug with long password
130$t->post_ok('/user/login' => form => {
Akrone208d302020-11-28 11:14:50 +0100131 handle => 'test',
Akron3e0fdc12020-05-15 16:17:21 +0200132 pwd => 'kjskjhndkjndqknaskjnakjdnkjdankajdnkjdsankjdsakjdfkjahzroiuqzriudjoijdmlamdlkmdsalkmdl' })
133 ->status_is(302)
134 ->header_is('Location' => '/');
135
Akrone208d302020-11-28 11:14:50 +0100136$t->post_ok('/user/login' => form => { handle => 'test', pwd => 'fail' })
Akron33f5c672019-06-24 19:40:47 +0200137 ->status_is(302)
138 ->header_is('Location' => '/');
139
140$t->get_ok('/')
141 ->status_is(200)
142 ->element_exists('div.notify-error')
143 ->text_is('div.notify-error', 'Bad CSRF token')
Akrone208d302020-11-28 11:14:50 +0100144 ->element_exists('input[name=handle][value=test]')
Akron33f5c672019-06-24 19:40:47 +0200145 ->element_exists_not('div.button.top a')
146 ;
147
Akrone208d302020-11-28 11:14:50 +0100148$t->post_ok('/user/login' => form => { handle => 'test', pwd => 'pass' })
Akron33f5c672019-06-24 19:40:47 +0200149 ->status_is(302)
150 ->header_is('Location' => '/');
151
152my $csrf = $t->get_ok('/')
153 ->status_is(200)
154 ->element_exists('div.notify-error')
155 ->text_is('div.notify-error', 'Bad CSRF token')
156 ->element_exists_not('div.button.top a')
157 ->tx->res->dom->at('input[name=csrf_token]')->attr('value')
158 ;
159
160$t->post_ok('/user/login' => form => {
Akrone208d302020-11-28 11:14:50 +0100161 handle => 'test',
Akron33f5c672019-06-24 19:40:47 +0200162 pwd => 'ldaperr',
163 csrf_token => $csrf
164})
165 ->status_is(302)
166 ->content_is('')
167 ->header_is('Location' => '/');
168
169$csrf = $t->get_ok('/')
170 ->status_is(200)
171 ->element_exists('div.notify-error')
172 ->text_is('div.notify-error', '2022: LDAP Authentication failed due to unknown user or password!')
Akrone208d302020-11-28 11:14:50 +0100173 ->element_exists('input[name=handle][value=test]')
Akron33f5c672019-06-24 19:40:47 +0200174 ->element_exists_not('div.button.top a')
Akron3b3c7af2020-05-15 16:23:55 +0200175 ->element_exists_not('div.notify-success')
Akron33f5c672019-06-24 19:40:47 +0200176 ->tx->res->dom->at('input[name=csrf_token]')->attr('value')
177 ;
178
179$t->post_ok('/user/login' => form => {
Akrone208d302020-11-28 11:14:50 +0100180 handle => 'test',
Akron33f5c672019-06-24 19:40:47 +0200181 pwd => 'unknown',
182 csrf_token => $csrf
183})
184 ->status_is(302)
185 ->content_is('')
186 ->header_is('Location' => '/');
187
188$csrf = $t->get_ok('/')
189 ->status_is(200)
190 ->element_exists('div.notify-error')
Akron8bbbecf2019-07-01 18:57:30 +0200191 ->text_is('div.notify-error', '2022: LDAP Authentication failed due to unknown user or password!')
Akrone208d302020-11-28 11:14:50 +0100192 ->element_exists('input[name=handle][value=test]')
Akron33f5c672019-06-24 19:40:47 +0200193 ->element_exists_not('div.button.top a')
194 ->tx->res->dom->at('input[name=csrf_token]')->attr('value')
195 ;
196
197$t->post_ok('/user/login' => form => {
Akrone208d302020-11-28 11:14:50 +0100198 handle => 'test',
Akron33f5c672019-06-24 19:40:47 +0200199 pwd => 'pass',
200 csrf_token => $csrf
201})
202 ->status_is(302)
203 ->content_is('')
204 ->header_is('Location' => '/');
205
206$t->get_ok('/')
207 ->status_is(200)
208 ->element_exists_not('div.notify-error')
209 ->element_exists('div.notify-success')
210 ->text_is('div.notify-success', 'Login successful')
Akron1d09b532021-06-15 18:18:25 +0200211 ->element_exists_not('aside.off')
Akrondc0b3ab2021-06-18 11:52:43 +0200212 ->element_exists_not('aside.active')
Akron1d09b532021-06-15 18:18:25 +0200213 ->element_exists('aside.settings')
Akron33f5c672019-06-24 19:40:47 +0200214 ;
215
Akron33f5c672019-06-24 19:40:47 +0200216# Now the user is logged in and should be able to
217# search with authorization
218$t->get_ok('/?q=Baum')
219 ->status_is(200)
Akron4cefe1f2019-09-04 10:11:28 +0200220 ->session_has('/auth')
221 ->session_is('/auth', 'Bearer ' . $access_token)
222 ->session_is('/auth_r', $refresh_token)
223 ->session_is('/user', 'test')
Akron33f5c672019-06-24 19:40:47 +0200224 ->text_like('h1 span', qr/KorAP: Find .Baum./i)
225 ->text_like('#total-results', qr/\d+$/)
226 ->element_exists_not('div.notify-error')
Akronbc6b3f22021-01-13 14:53:12 +0100227 ->content_like(qr/${q}authorized${q}:${q}yes${q}/)
Akron33f5c672019-06-24 19:40:47 +0200228 ->element_exists('div.button.top a')
229 ->element_exists('div.button.top a.logout[title~="test"]')
230 ;
231
Akron27031aa2020-04-28 14:57:10 +0200232$t->get_ok('/?q=Paum')
233 ->status_is(200)
234 ->text_like('h1 span', qr/KorAP: Find .Paum./i)
235 ->text_is('#total-results', '')
Akronbc6b3f22021-01-13 14:53:12 +0100236 ->content_like(qr/${q}authorized${q}:${q}yes${q}/)
Akron27031aa2020-04-28 14:57:10 +0200237 ->element_exists_not('p.hint')
238 ;
239
Akroncce055c2021-07-02 12:18:03 +0200240# Query with error
241$t->get_ok('/?q=error')
242 ->status_is(400)
243 ->text_is('#notifications .notify-error','500: Internal Server Error')
244;
Akron27031aa2020-04-28 14:57:10 +0200245
Akron33f5c672019-06-24 19:40:47 +0200246# Logout
247$t->get_ok('/user/logout')
248 ->status_is(302)
Akron4cefe1f2019-09-04 10:11:28 +0200249 ->session_hasnt('/auth')
250 ->session_hasnt('/auth_r')
251 ->session_hasnt('/user')
Akron33f5c672019-06-24 19:40:47 +0200252 ->header_is('Location' => '/');
253
254$t->get_ok('/')
255 ->status_is(200)
256 ->element_exists_not('div.notify-error')
257 ->element_exists('div.notify-success')
258 ->text_is('div.notify-success', 'Logout successful')
Akrone208d302020-11-28 11:14:50 +0100259 ->element_exists("input[name=handle]")
260 ->element_exists("input[name=handle][value=test]")
Akron33f5c672019-06-24 19:40:47 +0200261 ;
262
263$t->get_ok('/?q=Baum')
264 ->status_is(200)
265 ->text_like('h1 span', qr/KorAP: Find .Baum./i)
266 ->text_like('#total-results', qr/\d+$/)
Akronbc6b3f22021-01-13 14:53:12 +0100267 ->content_like(qr/${q}authorized${q}:null/)
Akron33f5c672019-06-24 19:40:47 +0200268 ;
269
Akron27031aa2020-04-28 14:57:10 +0200270$t->get_ok('/?q=Paum')
271 ->status_is(200)
272 ->text_like('h1 span', qr/KorAP: Find .Paum./i)
273 ->text_is('#total-results', '')
Akronbc6b3f22021-01-13 14:53:12 +0100274 ->content_like(qr/${q}authorized${q}:null/)
Akron27031aa2020-04-28 14:57:10 +0200275 ->text_is('p.hint', 'Maybe you need to log in first?')
276 ;
277
278
Akron33f5c672019-06-24 19:40:47 +0200279# Get redirect
280my $fwd = $t->get_ok('/?q=Baum&ql=poliqarp')
281 ->status_is(200)
282 ->element_exists_not('div.notify-error')
283 ->tx->res->dom->at('input[name=fwd]')->attr('value')
284 ;
285
286is($fwd, '/?q=Baum&ql=poliqarp', 'Redirect is valid');
287
288$t->post_ok('/user/login' => form => {
Akrone208d302020-11-28 11:14:50 +0100289 handle => 'test',
Akron33f5c672019-06-24 19:40:47 +0200290 pwd => 'pass',
291 csrf_token => $csrf,
292 fwd => 'http://bad.example.com/test'
293})
294 ->status_is(302)
295 ->header_is('Location' => '/');
296
297$t->get_ok('/')
298 ->status_is(200)
299 ->element_exists('div.notify-error')
300 ->element_exists_not('div.notify-success')
301 ->text_is('div.notify-error', 'Redirect failure')
302 ;
303
304$t->post_ok('/user/login' => form => {
Akrone208d302020-11-28 11:14:50 +0100305 handle => 'test',
Akron33f5c672019-06-24 19:40:47 +0200306 pwd => 'pass',
307 csrf_token => $csrf,
308 fwd => $fwd
309})
310 ->status_is(302)
311 ->header_is('Location' => '/?q=Baum&ql=poliqarp');
312
Akron8bbbecf2019-07-01 18:57:30 +0200313$t->get_ok('/?q=Baum&ql=poliqarp')
314 ->status_is(200)
315 ->element_exists_not('div.notify-error')
316 ->element_exists('div.notify-success')
317 ->text_is('div.notify-success', 'Login successful')
Akroncdfd9d52019-07-23 11:35:00 +0200318 ->session_has('/auth')
319 ->session_is('/auth', 'Bearer ' . $access_token)
320 ->session_is('/auth_r', $refresh_token)
321 ->header_isnt('X-Kalamar-Cache', 'true')
Akron8bbbecf2019-07-01 18:57:30 +0200322 ;
323
Akroncdfd9d52019-07-23 11:35:00 +0200324# Expire the session
325# (makes the token be marked as expired - though it isn't serverside)
326$t->get_ok('/x/expire')
Akron8bbbecf2019-07-01 18:57:30 +0200327 ->status_is(200)
Akroncdfd9d52019-07-23 11:35:00 +0200328 ->content_is('okay')
329 ;
330
331## It may be a problem, but the cache is still valid
332$t->get_ok('/?q=Baum')
333 ->status_is(200)
334 ->text_like('h1 span', qr/KorAP: Find .Baum./i)
335 ->text_like('#total-results', qr/\d+$/)
Akronbc6b3f22021-01-13 14:53:12 +0100336 ->content_like(qr/${q}authorized${q}:${q}yes${q}/)
Akroncdfd9d52019-07-23 11:35:00 +0200337 ->header_is('X-Kalamar-Cache', 'true')
338 ;
339
340# Query without partial cache (unfortunately) (but no total results)
Akron58c60992021-09-07 13:11:43 +0200341my $err = $t->get_ok('/?q=baum&cutoff=true')
Akroncdfd9d52019-07-23 11:35:00 +0200342 ->status_is(200)
343 ->session_is('/auth', 'Bearer ' . $access_token_2)
344 ->session_is('/auth_r', $refresh_token_2)
Akroncdfd9d52019-07-23 11:35:00 +0200345 ->text_is('title', 'KorAP: Find »baum« with Poliqarp')
346 ->element_exists('meta[name="DC.title"][content="KorAP: Find »baum« with Poliqarp"]')
347 ->element_exists('body[itemscope][itemtype="http://schema.org/SearchResultsPage"]')
Akronbc6b3f22021-01-13 14:53:12 +0100348 ->content_like(qr/${q}authorized${q}:${q}yes${q}/)
Akroncdfd9d52019-07-23 11:35:00 +0200349 ->header_isnt('X-Kalamar-Cache', 'true')
Akronbc6b3f22021-01-13 14:53:12 +0100350 ->content_like(qr!${q}cutOff${q}:true!)
Akroncdfd9d52019-07-23 11:35:00 +0200351 ->element_exists_not('#total-results')
Akron58c60992021-09-07 13:11:43 +0200352 ->tx->res->dom->at('#error')
Akroncdfd9d52019-07-23 11:35:00 +0200353 ;
Akron58c60992021-09-07 13:11:43 +0200354is(defined $err ? $err->text : '', '');
Akroncdfd9d52019-07-23 11:35:00 +0200355
356# Expire the session and remove the refresh option
357$t->get_ok('/x/expire-no-refresh')
358 ->status_is(200)
359 ->content_is('okay')
360 ;
361
362$t->app->defaults(no_cache => 1);
363
364
365$t->get_ok('/x/invalid-no-refresh')
366 ->status_is(200)
367 ->content_is('okay')
368 ;
369
370# Query without cache
371# The token is invalid and can't be refreshed!
Akron58c60992021-09-07 13:11:43 +0200372$err = $t->get_ok('/?q=baum&cutoff=true')
Akron3c390c42020-03-30 09:06:21 +0200373 ->status_is(400)
Akroncdfd9d52019-07-23 11:35:00 +0200374 ->session_hasnt('/auth')
375 ->session_hasnt('/auth_r')
Akroncdfd9d52019-07-23 11:35:00 +0200376 ->text_is('div.notify-error','Access token invalid')
377 ->text_is('title', 'KorAP: Find »baum« with Poliqarp')
378 ->element_exists('meta[name="DC.title"][content="KorAP: Find »baum« with Poliqarp"]')
379 ->element_exists('body[itemscope][itemtype="http://schema.org/SearchResultsPage"]')
Akronbc6b3f22021-01-13 14:53:12 +0100380 ->content_unlike(qr/${q}authorized${q}:${q}yes${q}/)
Akroncdfd9d52019-07-23 11:35:00 +0200381 ->header_isnt('X-Kalamar-Cache', 'true')
382 ->element_exists('p.no-results')
Akron58c60992021-09-07 13:11:43 +0200383 ->tx->res->dom->at('#error')
Akroncdfd9d52019-07-23 11:35:00 +0200384 ;
Akron58c60992021-09-07 13:11:43 +0200385is(defined $err ? $err->text : '', '');
386
Akroncdfd9d52019-07-23 11:35:00 +0200387
388$t->get_ok('/x/invalid')
389 ->status_is(200)
390 ->content_is('okay')
391 ;
392
393# Query without cache
394# The token is invalid and can't be refreshed!
Akron58c60992021-09-07 13:11:43 +0200395$err = $t->get_ok('/?q=baum&cutoff=true')
Akroncdfd9d52019-07-23 11:35:00 +0200396 ->status_is(200)
397 ->session_is('/auth', 'Bearer ' . $access_token_2)
398 ->session_is('/auth_r', $refresh_token_2)
Akroncdfd9d52019-07-23 11:35:00 +0200399 ->element_exists_not('div.notify-error','Access token invalid')
400 ->text_is('title', 'KorAP: Find »baum« with Poliqarp')
401 ->element_exists('meta[name="DC.title"][content="KorAP: Find »baum« with Poliqarp"]')
402 ->element_exists('body[itemscope][itemtype="http://schema.org/SearchResultsPage"]')
Akronbc6b3f22021-01-13 14:53:12 +0100403 ->content_like(qr/${q}authorized${q}:${q}yes${q}/)
Akroncdfd9d52019-07-23 11:35:00 +0200404 ->header_isnt('X-Kalamar-Cache', 'true')
405 ->element_exists_not('p.no-results')
Akron58c60992021-09-07 13:11:43 +0200406 ->tx->res->dom->at('#error')
Akron8bbbecf2019-07-01 18:57:30 +0200407 ;
Akron58c60992021-09-07 13:11:43 +0200408is(defined $err ? $err->text : '', '');
Akron8bbbecf2019-07-01 18:57:30 +0200409
Akron33f5c672019-06-24 19:40:47 +0200410
Akroncdfd9d52019-07-23 11:35:00 +0200411$t->get_ok('/x/expired-with-wrong-refresh')
412 ->status_is(200)
413 ->content_is('okay')
414 ;
Akron4796e002019-07-05 10:13:15 +0200415
Akron4796e002019-07-05 10:13:15 +0200416
Akroncdfd9d52019-07-23 11:35:00 +0200417# The token is invalid and can't be refreshed!
Akron58c60992021-09-07 13:11:43 +0200418my $dom = $t->get_ok('/?q=baum&cutoff=true')
Akron3c390c42020-03-30 09:06:21 +0200419 ->status_is(400)
Akroncdfd9d52019-07-23 11:35:00 +0200420 ->session_hasnt('/auth')
421 ->session_hasnt('/auth_r')
Akroncdfd9d52019-07-23 11:35:00 +0200422 ->text_is('div.notify-error','Refresh token is expired')
423 ->text_is('title', 'KorAP: Find »baum« with Poliqarp')
Akronbc6b3f22021-01-13 14:53:12 +0100424 ->content_unlike(qr/${q}authorized${q}:${q}yes${q}/)
Akroncdfd9d52019-07-23 11:35:00 +0200425 ->element_exists('p.no-results')
Akron58c60992021-09-07 13:11:43 +0200426 ->tx->res->dom;
427
428$csrf = $dom->at('input[name="csrf_token"]')
Akron59992122019-10-29 11:28:45 +0100429 ->attr('value')
Akroncdfd9d52019-07-23 11:35:00 +0200430 ;
Akron4796e002019-07-05 10:13:15 +0200431
Akron58c60992021-09-07 13:11:43 +0200432$err = $dom->at('#error');
433is(defined $err ? $err->text : '', '');
434
435
Akron6a228db2021-10-14 15:57:00 +0200436# This should fail
437my $wide_char_login = "\x{61}\x{E5}\x{61}"; # "\x{443}\x{434}";
Akron59992122019-10-29 11:28:45 +0100438$t->post_ok('/user/login' => form => {
Akron6a228db2021-10-14 15:57:00 +0200439 handle => $wide_char_login,
440 pwd => 'pass',
441 csrf_token => $csrf,
442 fwd => $fwd
443})
444 ->status_is(302)
445 ->header_is('Location' => '/');
446
447$t->get_ok('/')
448 ->status_is(200)
449 ->element_exists('div.notify-error')
450 ->text_is('div.notify-error', 'Invalid character in request')
451 ->element_exists('input[name=handle]:not([value])')
452 ->element_exists_not('div.button.top a')
453 ;
454
455# Login:
456# UTF8 request
457my $username = b('täst')->encode;
458$t->post_ok('/user/login' => form => {
459 handle => $username,
Akron59992122019-10-29 11:28:45 +0100460 pwd => 'pass',
461 csrf_token => $csrf
462})
463 ->status_is(302)
464 ->content_is('')
465 ->header_is('Location' => '/');
466
467$t->get_ok('/')
468 ->status_is(200)
469 ->element_exists_not('div.notify-error')
470 ->element_exists('div.notify-success')
471 ->text_is('div.notify-success', 'Login successful')
Akron6a228db2021-10-14 15:57:00 +0200472 ->attr_is('a.logout', 'title', "Logout: $username")
Akron1d09b532021-06-15 18:18:25 +0200473 ->element_exists_not('aside.off')
Akrondc0b3ab2021-06-18 11:52:43 +0200474 ->element_exists_not('aside.active')
Akron1d09b532021-06-15 18:18:25 +0200475 ->element_exists('aside.settings')
Akron59992122019-10-29 11:28:45 +0100476 ;
477
478$t->get_ok('/settings/oauth')
479 ->text_is('form.form-table legend', 'Register new client application')
480 ->attr_is('form.oauth-register','action', '/settings/oauth/register')
Akron1a9d5be2020-03-19 17:28:33 +0100481 ->element_exists('ul.client-list')
482 ->element_exists_not('ul.client-list > li')
Akronad011bb2021-06-10 12:16:36 +0200483 ->header_is('Cache-Control','max-age=0, no-cache, no-store, must-revalidate')
484 ->header_is('Expires','Thu, 01 Jan 1970 00:00:00 GMT')
485 ->header_is('Pragma','no-cache')
Akron59992122019-10-29 11:28:45 +0100486 ;
Akronad011bb2021-06-10 12:16:36 +0200487
Akron59992122019-10-29 11:28:45 +0100488$csrf = $t->post_ok('/settings/oauth/register' => form => {
489 name => 'MyApp',
490 type => 'PUBLIC',
491 desc => 'This is my application'
492})
493 ->text_is('div.notify-error', 'Bad CSRF token')
494 ->tx->res->dom->at('input[name="csrf_token"]')
495 ->attr('value')
496 ;
497
498$t->post_ok('/settings/oauth/register' => form => {
499 name => 'MyApp',
500 type => 'CONFIDENTIAL',
501 desc => 'This is my application',
502 csrf_token => $csrf
503})
504 ->status_is(200)
505 ->element_exists('div.notify-success')
506 ->text_is('legend', 'Client Credentials')
507 ->text_is('label[for=client_id]', 'ID of the client application')
508 ->element_exists('input[name=client_id][readonly][value]')
509 ->element_exists('input[name=client_secret][readonly][value]')
Akronad011bb2021-06-10 12:16:36 +0200510 ->header_is('Cache-Control','max-age=0, no-cache, no-store, must-revalidate')
511 ->header_is('Expires','Thu, 01 Jan 1970 00:00:00 GMT')
512 ->header_is('Pragma','no-cache')
Akron59992122019-10-29 11:28:45 +0100513 ;
Akron4796e002019-07-05 10:13:15 +0200514
Akron58c60992021-09-07 13:11:43 +0200515my $anchor = $t->get_ok('/settings/oauth')
Akronc1aaf932021-06-09 12:19:15 +0200516 ->text_is('.form-table legend', 'Register new client application')
517 ->attr_is('.oauth-register','action', '/settings/oauth/register')
Akron17de86e2020-04-16 16:03:40 +0200518 ->text_is('ul.client-list > li > span.client-name a', 'MyApp')
Akronb6b156e2022-03-31 14:57:49 +0200519 ->text_is('ul.client-list > li > p.client-desc', 'This is my application')
Akronad011bb2021-06-10 12:16:36 +0200520 ->header_is('Cache-Control','max-age=0, no-cache, no-store, must-revalidate')
521 ->header_is('Expires','Thu, 01 Jan 1970 00:00:00 GMT')
522 ->header_is('Pragma','no-cache')
Akronb6b156e2022-03-31 14:57:49 +0200523 ->tx->res->dom->at('ul.client-list > li > p.client-url a')
Akron17de86e2020-04-16 16:03:40 +0200524 ;
Akron58c60992021-09-07 13:11:43 +0200525is(defined $anchor ? $anchor->text : '', '');
Akron17de86e2020-04-16 16:03:40 +0200526
Akron041ca4d2021-06-10 11:52:51 +0200527$t->get_ok('/settings/oauth/fCBbQkA2NDA3MzM1Yw==')
Akron17de86e2020-04-16 16:03:40 +0200528 ->status_is(200)
Akronc1aaf932021-06-09 12:19:15 +0200529 ->text_is('ul.client-list > li.client > span.client-name', 'MyApp')
Akronb6b156e2022-03-31 14:57:49 +0200530 ->text_is('ul.client-list > li.client > p.client-desc', 'This is my application')
Akron17de86e2020-04-16 16:03:40 +0200531 ->text_is('a.client-unregister', 'Unregister')
Akron041ca4d2021-06-10 11:52:51 +0200532 ->attr_is('a.client-unregister', 'href', '/settings/oauth/fCBbQkA2NDA3MzM1Yw==/unregister?name=MyApp')
Akron1a9d5be2020-03-19 17:28:33 +0100533 ;
534
Akron041ca4d2021-06-10 11:52:51 +0200535$csrf = $t->get_ok('/settings/oauth/fCBbQkA2NDA3MzM1Yw==/unregister?name=MyApp')
Akron1a9d5be2020-03-19 17:28:33 +0100536 ->content_like(qr!Do you really want to unregister \<span class="client-name"\>MyApp\<\/span\>?!)
Akronc1aaf932021-06-09 12:19:15 +0200537 ->attr_is('.form-table input[name=client-name]', 'value', 'MyApp')
Akronad011bb2021-06-10 12:16:36 +0200538 ->header_is('Cache-Control','max-age=0, no-cache, no-store, must-revalidate')
539 ->header_is('Expires','Thu, 01 Jan 1970 00:00:00 GMT')
540 ->header_is('Pragma','no-cache')
Akron1a9d5be2020-03-19 17:28:33 +0100541 ->tx->res->dom->at('input[name="csrf_token"]')
542 ->attr('value')
543 ;
544
Akron041ca4d2021-06-10 11:52:51 +0200545$t->post_ok('/settings/oauth/xxxx==/unregister' => form => {
Akron1a9d5be2020-03-19 17:28:33 +0100546 'client-name' => 'MyApp',
Akron1a9d5be2020-03-19 17:28:33 +0100547 'csrf_token' => $csrf
548})->status_is(302)
549 ->content_is('')
550 ->header_is('Location' => '/settings/oauth')
551 ;
552
553$t->get_ok('/settings/oauth')
Akronc1aaf932021-06-09 12:19:15 +0200554 ->text_is('.form-table legend', 'Register new client application')
555 ->attr_is('.oauth-register','action', '/settings/oauth/register')
Akron1a9d5be2020-03-19 17:28:33 +0100556 ->element_exists('ul.client-list > li')
557 ->text_is('div.notify', 'Unknown client with xxxx==.')
Akronad011bb2021-06-10 12:16:36 +0200558 ->header_is('Cache-Control','max-age=0, no-cache, no-store, must-revalidate')
559 ->header_is('Expires','Thu, 01 Jan 1970 00:00:00 GMT')
560 ->header_is('Pragma','no-cache')
Akron1a9d5be2020-03-19 17:28:33 +0100561 ;
562
Akron041ca4d2021-06-10 11:52:51 +0200563$t->post_ok('/settings/oauth/fCBbQkA2NDA3MzM1Yw==/unregister' => form => {
Akron1a9d5be2020-03-19 17:28:33 +0100564 'client-name' => 'MyApp',
Akron1a9d5be2020-03-19 17:28:33 +0100565 'csrf_token' => $csrf
566})->status_is(302)
567 ->content_is('')
568 ->header_is('Location' => '/settings/oauth')
569 ;
570
571$t->get_ok('/settings/oauth')
Akronc1aaf932021-06-09 12:19:15 +0200572 ->text_is('.form-table legend', 'Register new client application')
573 ->attr_is('.oauth-register','action', '/settings/oauth/register')
Akron1a9d5be2020-03-19 17:28:33 +0100574 ->element_exists_not('ul.client-list > li')
575 ->text_is('div.notify-success', 'Successfully deleted MyApp')
Akronad011bb2021-06-10 12:16:36 +0200576 ->header_is('Cache-Control','max-age=0, no-cache, no-store, must-revalidate')
577 ->header_is('Expires','Thu, 01 Jan 1970 00:00:00 GMT')
578 ->header_is('Pragma','no-cache')
Akron1a9d5be2020-03-19 17:28:33 +0100579 ;
580
Akron83209f72021-01-29 17:54:15 +0100581$t->post_ok('/settings/oauth/register' => form => {
582 name => 'MyApp2',
583 type => 'PUBLIC',
584 desc => 'This is my application',
585 csrf_token => $csrf
586})->status_is(200)
587 ->element_exists('div.notify-success')
588 ->text_is('legend', 'Client Credentials')
589 ->text_is('label[for=client_id]', 'ID of the client application')
590 ->element_exists('input[name=client_id][readonly][value]')
591 ->element_exists_not('input[name=client_secret][readonly][value]')
Akronad011bb2021-06-10 12:16:36 +0200592 ->header_is('Cache-Control','max-age=0, no-cache, no-store, must-revalidate')
593 ->header_is('Expires','Thu, 01 Jan 1970 00:00:00 GMT')
594 ->header_is('Pragma','no-cache')
Akronb6b156e2022-03-31 14:57:49 +0200595 ->element_exists('.client-issue-token')
Akron83209f72021-01-29 17:54:15 +0100596 ;
597
Akron041ca4d2021-06-10 11:52:51 +0200598$t->get_ok('/settings/oauth/fCBbQkA2NDA3MzM1Yw==')
Akron83209f72021-01-29 17:54:15 +0100599 ->text_is('.client-name', 'MyApp2')
600 ->text_is('.client-desc', 'This is my application')
Akrone997bb52021-06-11 16:44:06 +0200601 ->text_is('.client-issue-token', 'Issue new token')
Akron041ca4d2021-06-10 11:52:51 +0200602 ->attr_is('.client-issue-token', 'href', '/settings/oauth/fCBbQkA2NDA3MzM1Yw==/token?name=MyApp2')
Akronad011bb2021-06-10 12:16:36 +0200603 ->header_is('Cache-Control','max-age=0, no-cache, no-store, must-revalidate')
604 ->header_is('Expires','Thu, 01 Jan 1970 00:00:00 GMT')
605 ->header_is('Pragma','no-cache')
Akrone997bb52021-06-11 16:44:06 +0200606 ->text_is('ul.token-list label[for=token]', 'Access Token')
607 ->text_is('p[name=created]', 'Created at ')
608 ->text_is('p[name=expires]', 'Expires in 31533851 seconds.')
Akronb6b156e2022-03-31 14:57:49 +0200609 ->element_exists('.client-issue-token')
Akron83209f72021-01-29 17:54:15 +0100610 ;
611
Akron041ca4d2021-06-10 11:52:51 +0200612$csrf = $t->get_ok('/settings/oauth/fCBbQkA2NDA3MzM1Yw==/token?name=MyApp2')
Akron83209f72021-01-29 17:54:15 +0100613 ->status_is(200)
Akron041ca4d2021-06-10 11:52:51 +0200614 ->attr_is('#issue-token','action', '/settings/oauth/fCBbQkA2NDA3MzM1Yw==/token')
Akron83209f72021-01-29 17:54:15 +0100615 ->attr_is('input[name=client-id]', 'value', 'fCBbQkA2NDA3MzM1Yw==')
616 ->attr_is('input[name=name]', 'value', 'MyApp2')
Akronad011bb2021-06-10 12:16:36 +0200617 ->header_is('Cache-Control','max-age=0, no-cache, no-store, must-revalidate')
618 ->header_is('Expires','Thu, 01 Jan 1970 00:00:00 GMT')
619 ->header_is('Pragma','no-cache')
Akrone997bb52021-06-11 16:44:06 +0200620 ->text_is('a.button-abort', 'Abort')
621 ->attr_is('#issue-token input[type=submit]', 'value', 'Issue new token')
622 ->content_like(qr!Issue a new token for!)
Akron83209f72021-01-29 17:54:15 +0100623 ->tx->res->dom->at('input[name="csrf_token"]')
624 ->attr('value')
625 ;
626
Akron041ca4d2021-06-10 11:52:51 +0200627$t->post_ok('/settings/oauth/fCBbQkA2NDA3MzM1Yw==/token' => form => {
Akron83209f72021-01-29 17:54:15 +0100628 csrf_token => $csrf,
629 name => 'MyApp2',
630 'client-id' => 'fCBbQkA2NDA3MzM1Yw=='
631})
Akronbc94a9c2021-04-15 00:07:35 +0200632 ->status_is(302)
Akron041ca4d2021-06-10 11:52:51 +0200633 ->header_is('Location','/settings/oauth/fCBbQkA2NDA3MzM1Yw==')
Akronbc94a9c2021-04-15 00:07:35 +0200634 ;
635
Akron041ca4d2021-06-10 11:52:51 +0200636$t->get_ok('/settings/oauth/fCBbQkA2NDA3MzM1Yw==')
637 ->status_is(200)
Akronbc94a9c2021-04-15 00:07:35 +0200638 ->text_is('div.notify-success', 'New access token created')
Akronad011bb2021-06-10 12:16:36 +0200639 ->status_is(200)
640 ->header_is('Cache-Control','max-age=0, no-cache, no-store, must-revalidate')
641 ->header_is('Expires','Thu, 01 Jan 1970 00:00:00 GMT')
642 ->header_is('Pragma','no-cache')
Akron83209f72021-01-29 17:54:15 +0100643 ;
644
Akron041ca4d2021-06-10 11:52:51 +0200645$csrf = $t->get_ok('/settings/oauth/fCBbQkA2NDA3MzM1Yw==')
Akronc1aaf932021-06-09 12:19:15 +0200646 ->status_is(200)
Akron041ca4d2021-06-10 11:52:51 +0200647 ->attr_is('form.token-revoke', 'action', '/settings/oauth/fCBbQkA2NDA3MzM1Yw==/token/revoke')
Akronc1aaf932021-06-09 12:19:15 +0200648 ->attr_is('form.token-revoke input[name=token]', 'value', 'jhkhkjhk_hjgjsfz67i')
649 ->attr_is('form.token-revoke input[name=name]', 'value', 'MyApp2')
650 ->tx->res->dom->at('input[name="csrf_token"]')
651 ->attr('value')
652 ;
653
Akron041ca4d2021-06-10 11:52:51 +0200654$t->post_ok('/settings/oauth/fCBbQkA2NDA3MzM1Yw==/token/revoke' => form => {
Akronc1aaf932021-06-09 12:19:15 +0200655 csrf_token => $csrf,
656 name => 'MyApp2',
657 token => 'jhkhkjhk_hjgjsfz67i'
658})
659 ->status_is(200)
Akron041ca4d2021-06-10 11:52:51 +0200660 ->attr_is('form#revoke-token','action','/settings/oauth/fCBbQkA2NDA3MzM1Yw==/token?_method=DELETE')
Akronc1aaf932021-06-09 12:19:15 +0200661 ->attr_is('form#revoke-token','method','POST')
662 ->attr_is('form#revoke-token input[name=token]','value','jhkhkjhk_hjgjsfz67i')
Akrone997bb52021-06-11 16:44:06 +0200663 ->text_is('a.button-abort', 'Abort')
664 ->attr_is('#revoke-token input[type=submit]', 'value', 'Revoke')
665 ->content_like(qr!Revoke a token for!)
Akronc1aaf932021-06-09 12:19:15 +0200666;
667
668
669# CSRF missing
Akron041ca4d2021-06-10 11:52:51 +0200670$t->post_ok('/settings/oauth/fCBbQkA2NDA3MzM1Yw==/token?_method=DELETE' => form => {
Akronc1aaf932021-06-09 12:19:15 +0200671 name => 'MyApp2',
672 token => 'jhkhkjhk_hjgjsfz67i'
673})->status_is(302)
Akron041ca4d2021-06-10 11:52:51 +0200674 ->header_is('Location','/settings/oauth/fCBbQkA2NDA3MzM1Yw==')
Akronc1aaf932021-06-09 12:19:15 +0200675 ;
676
Akron041ca4d2021-06-10 11:52:51 +0200677$t->get_ok('/settings/oauth/fCBbQkA2NDA3MzM1Yw==')
Akronc1aaf932021-06-09 12:19:15 +0200678 ->element_exists_not('div.notify-success')
679 ->text_is('div.notify-error', 'Bad CSRF token')
680 ;
681
682# Token missing
Akron041ca4d2021-06-10 11:52:51 +0200683$t->post_ok('/settings/oauth/fCBbQkA2NDA3MzM1Yw==/token?_method=DELETE' => form => {
Akronc1aaf932021-06-09 12:19:15 +0200684 name => 'MyApp2',
685 csrf_token => $csrf,
686})->status_is(302)
Akron041ca4d2021-06-10 11:52:51 +0200687 ->header_is('Location','/settings/oauth/fCBbQkA2NDA3MzM1Yw==')
Akronc1aaf932021-06-09 12:19:15 +0200688 ;
689
Akron041ca4d2021-06-10 11:52:51 +0200690$t->get_ok('/settings/oauth/fCBbQkA2NDA3MzM1Yw==')
Akronc1aaf932021-06-09 12:19:15 +0200691 ->element_exists_not('div.notify-success')
692 ->text_is('div.notify-error', 'Some fields are invalid')
693 ;
694
Akron041ca4d2021-06-10 11:52:51 +0200695$t->post_ok('/settings/oauth/fCBbQkA2NDA3MzM1Yw==/token?_method=DELETE' => form => {
Akronc1aaf932021-06-09 12:19:15 +0200696 name => 'MyApp2',
697 csrf_token => $csrf,
698 token => 'jhkhkjhk_hjgjsfz67i'
699})->status_is(302)
Akron041ca4d2021-06-10 11:52:51 +0200700 ->header_is('Location','/settings/oauth/fCBbQkA2NDA3MzM1Yw==')
Akronc1aaf932021-06-09 12:19:15 +0200701 ;
702
Akron041ca4d2021-06-10 11:52:51 +0200703$t->get_ok('/settings/oauth/fCBbQkA2NDA3MzM1Yw==')
Akronc1aaf932021-06-09 12:19:15 +0200704 ->element_exists_not('div.notify-error')
705 ->text_is('div.notify-success', 'Token was revoked successfully')
706 ;
Akron83209f72021-01-29 17:54:15 +0100707
Akronb6b156e2022-03-31 14:57:49 +0200708$t->app->routes->get('/x/redirect-target')->to(
709 cb => sub {
710 my $c = shift;
711 return $c->render(text => 'redirected');
712 }
713);
714
715$csrf = $t->post_ok('/settings/oauth/register' => form => {
716 name => 'MyConfApp',
717 type => 'CONFIDENTIAL',
718 desc => 'This is my application',
719})
720 ->text_is('div.notify-error', 'Bad CSRF token')
721 ->tx->res->dom->at('input[name="csrf_token"]')
722 ->attr('value')
723 ;
724
725$t->post_ok('/settings/oauth/register' => form => {
726 name => 'MyConfApp',
727 type => 'CONFIDENTIAL',
728 desc => 'This is my confidential application',
729 csrf_token => $csrf,
730 redirect_uri => 'http://localhost/redirect-target'
731})
732 ->text_is('div.notify-error', undef)
733 ->text_is('li.client span.client-name', 'MyConfApp')
734 ->text_is('li.client p.client-desc', 'This is my confidential application')
735 ->text_is('li.client .client-redirect-uri tt', 'http://localhost/redirect-target')
736 ->text_is('li.client .client-type tt', 'CONFIDENTIAL')
737 ->element_exists_not('.client-issue-token')
738 ;
739
740$t->post_ok('/settings/oauth/register' => form => {
741 name => 'MyConfApp2',
742 type => 'CONFIDENTIAL',
743 desc => 'This is my second confidential application',
744 csrf_token => $csrf,
745 redirect_uri => 'http://localhost/FAIL'
746})
747 ->status_is(302)
748 ->header_is('location','/settings/oauth/')
749 ;
750
751$t->get_ok('/settings/oauth/')
752 ->text_is('div.notify-error', 'invalid_request: http://localhost/FAIL is invalid.')
753 ;
754
Akrona8efaa92022-04-09 14:45:43 +0200755# OAuth client authorization flow
756$t->get_ok(Mojo::URL->new('/settings/oauth/authorize'))
757 ->status_is(302)
758 ->header_is('location','/settings/oauth/authorize')
759 ;
760
761# Logout
762$t->get_ok('/x/expired-with-wrong-refresh');
763
764$t->get_ok('/user/logout')
765 ->status_is(302)
766 ->session_hasnt('/auth')
767 ->session_hasnt('/auth_r')
768 ->session_hasnt('/user')
769 ->header_is('Location' => '/');
770
771$csrf = $t->get_ok('/')
772 ->status_is(200)
773 ->element_exists_not('div.notify-error')
774 ->element_exists('div.notify-success')
775 ->text_is('div.notify-success', 'Logout successful')
776 ->element_exists("input[name=handle]")
777 ->tx->res->dom->at('input[name=csrf_token]')->attr('value')
778 ;
779
780$fwd = $t->get_ok(Mojo::URL->new('/settings/oauth/authorize')->query({
781 client_id => 'xyz',
782 state => 'abcde',
783 scope => 'search match',
784 redirect_uri => 'http://test.com/',
785}))
786 ->status_is(200)
787 ->attr_is('input[name=client_id]','value','xyz')
788 ->attr_is('input[name=state]','value','abcde')
789 ->attr_is('input[name=name]','value','xyz')
790 ->attr_like('input[name=fwd]','value',qr!test\.com!)
791 ->text_is('span.client-name','xyz')
792 ->text_is('div.intro p:nth-child(2)', 'Please log in!')
793 ->tx->res->dom->at('input[name=fwd]')->attr('value')
794 ;
795
796$fwd = $t->post_ok(Mojo::URL->new('/user/login')->query({
797 csrf_token => $csrf,
798 client_id => 'xyz',
799 state => 'abcde',
800 scope => 'search match',
801 redirect_uri => 'http://test.com/',
802 handle => 'test',
803 pwd => 'pass',
804 fwd => $fwd
805}))
806 ->status_is(302)
807 ->header_like('location', qr!/settings/oauth/authorize!)
808 ->tx->res->headers->header('location')
809 ;
810
811$t->get_ok($fwd)
812 ->status_is(200)
813 ->attr_is('input[name=client_id]','value','xyz')
814 ->attr_is('input[name=state]','value','abcde')
815 ->attr_is('input[name=name]','value','xyz')
816 ->text_is('ul#scopes li:nth-child(1)','search')
817 ->text_is('ul#scopes li:nth-child(2)','match')
818 ->text_is('span.client-name','xyz')
819 ->attr_is('a.form-button','href','http://test.com/')
820 ->attr_is('a.embedded-link', 'href', '/doc/korap/kalamar')
821 ;
822
823$t->get_ok(Mojo::URL->new('/settings/oauth/authorize')->query({
824 client_id => 'xyz',
825 state => 'abcde',
826 scope => 'search match',
827 redirect_uri => 'http://test.com/'
828}))
829 ->status_is(200)
830 ->attr_is('input[name=client_id]','value','xyz')
831 ->attr_is('input[name=state]','value','abcde')
832 ->attr_is('input[name=name]','value','xyz')
833 ->text_is('ul#scopes li:nth-child(1)','search')
834 ->text_is('ul#scopes li:nth-child(2)','match')
835 ->text_is('span.client-name','xyz')
836 ->attr_is('a.form-button','href','http://test.com/')
837 ->attr_is('a.embedded-link', 'href', '/doc/korap/kalamar')
838 ;
839
840$t->post_ok(Mojo::URL->new('/settings/oauth/authorize')->query({
841 client_id => 'xyz',
842 state => 'abcde',
843 scope => 'search match',
844 redirect_uri => 'http://test.com/'
845}))
846 ->status_is(302)
847 ->header_is('location', '/?error_description=Bad+CSRF+token')
848 ;
849
850$fwd = $t->post_ok(Mojo::URL->new('/settings/oauth/authorize')->query({
851 client_id => 'xyz',
852 state => 'abcde',
853 scope => 'search match',
854 redirect_uri_server => 'http://example.com/',
855 redirect_uri => $fake_backend_app->url_for('return_uri')->to_abs,
856 csrf_token => $csrf,
857}))
858 ->status_is(302)
859 ->header_like('location', qr!/realapi/fakeclient/return!)
860 ->tx->res->headers->header('location')
861 ;
862
863$t->get_ok($fwd)
864 ->status_is(200)
865 ->content_like(qr'welcome back! \[(.+?)\]')
866 ;
867
868$t->post_ok(Mojo::URL->new('/settings/oauth/authorize')->query({
869 client_id => 'xyz',
870 state => 'fail',
871 scope => 'search match',
872 redirect_uri_server => 'http://example.com/',
873 redirect_uri => $fake_backend_app->url_for('return_uri')->to_abs,
874 csrf_token => $csrf,
875}))
876 ->status_is(302)
877 ->header_is('location', 'http://example.com/?error_description=FAIL')
878 ;
879
Akronb6b156e2022-03-31 14:57:49 +0200880
Akron33f5c672019-06-24 19:40:47 +0200881done_testing;
882__END__
Akrona8efaa92022-04-09 14:45:43 +0200883
884