blob: 1af2fb0878057dc73c70e58129abd961fb639e76 [file] [log] [blame]
Akron0e1ed242018-10-11 13:22:00 +02001#!/usr/bin/env perl
2use Mojolicious::Lite;
3use Mojo::ByteStream 'b';
4use Mojo::Date;
5use Mojo::JSON qw/true false encode_json decode_json/;
6use strict;
7use warnings;
Akron6d49c1f2018-10-11 14:22:21 +02008use Mojo::File qw/path/;
9use Mojo::Util qw/slugify/;
Akron2fc697a2024-06-28 10:35:10 +020010use Kalamar::Controller::Search;
11
12our @default_search_fields = @Kalamar::Controller::Search::search_fields;
Akron0e1ed242018-10-11 13:22:00 +020013
14# This is an API fake server with fixtures
15
16my $secret = 's3cr3t';
Akron73f36082018-10-25 15:34:59 +020017my $fixture_path = path(Mojo::File->new(__FILE__)->dirname)->child('..', 'fixtures');
Akron0e1ed242018-10-11 13:22:00 +020018
Akroncdfd9d52019-07-23 11:35:00 +020019our %tokens = (
Akron59992122019-10-29 11:28:45 +010020 'access_token' => "4dcf8784ccfd26fac9bdb82778fe60e2",
21 'refresh_token' => "hlWci75xb8atDiq3924NUSvOdtAh7Nlf9z",
22 'access_token_2' => "abcde",
Akron83209f72021-01-29 17:54:15 +010023 'access_token_3' => 'jvgjbvjgzucgdwuiKHJK',
Akron59992122019-10-29 11:28:45 +010024 'refresh_token_2' => "fghijk",
25 'new_client_id' => 'fCBbQkA2NDA3MzM1Yw==',
Akronb6b156e2022-03-31 14:57:49 +020026 'new_client_id_2' => 'hghGHhjhFRz_gJhjrd==',
Akron6b75d122022-05-12 17:39:05 +020027 'new_client_id_3' => 'jh0gfjhjbfdsgzjghj==',
Akron59992122019-10-29 11:28:45 +010028 'new_client_secret' => 'KUMaFxs6R1WGud4HM22w3HbmYKHMnNHIiLJ2ihaWtB4N5JxGzZgyqs5GTLutrORj',
Akron83209f72021-01-29 17:54:15 +010029 'auth_token_1' => 'mscajfdghnjdfshtkjcuynxahgz5il'
Akroncdfd9d52019-07-23 11:35:00 +020030);
31
32helper get_token => sub {
33 my ($c, $token) = @_;
34 return $tokens{$token}
35};
36
Akroncdfd9d52019-07-23 11:35:00 +020037# Expiration helper
38helper expired => sub {
39 my ($c, $auth, $set) = @_;
40
Akroncdfd9d52019-07-23 11:35:00 +020041 $auth =~ s/^[^ ]+? //;
42 if ($set) {
43 $c->app->log->debug("Set $auth for expiration");
44 $c->app->defaults('auth_' . $auth => 1);
45 return 1;
46 };
47
48 $c->app->log->debug("Check $auth for expiration: " . (
49 $c->app->defaults('auth_' . $auth) // '0'
50 ));
51
52 return $c->app->defaults('auth_' . $auth);
53};
Akron0e1ed242018-10-11 13:22:00 +020054
Akron408bc7c2022-04-28 15:46:43 +020055
56helper 'add_client' => sub {
57 my $c = shift;
58 my $client = shift;
Akrondb1f4672023-01-24 12:05:07 +010059 my $list = $c->stash('oauth.client_list');
Akron408bc7c2022-04-28 15:46:43 +020060 push @$list, $client;
61};
62
Helge278fbca2022-11-29 18:49:15 +010063# Add plugin to plugin list for marketplace
64helper 'add_plugin' => sub {
65 my $c = shift;
66 my $cplugin = shift;
67 my $pl_list = $c->app->defaults('oauth.plugin_list');
68 push @$pl_list, $cplugin;
69};
Akron408bc7c2022-04-28 15:46:43 +020070
Helgedb720ea2023-03-20 09:39:36 +010071helper 'add_instplugin' => sub {
72 my $c = shift;
73 my $cplugin = shift;
74 my $pl_list = $c->app->defaults('oauth.pluginin_list');
75 push @$pl_list, $cplugin;
76};
77
78
Akron6d49c1f2018-10-11 14:22:21 +020079# Load fixture responses
80helper 'load_response' => sub {
81 my $c = shift;
82 my $q_name = shift;
83 my $file = $fixture_path->child("response_$q_name.json");
Akron8ea84292018-10-24 13:41:52 +020084 $c->app->log->debug("Load response from $file");
85
Akron6d49c1f2018-10-11 14:22:21 +020086 unless (-f $file) {
87 return {
88 status => 500,
89 json => {
90 errors => [[0, 'Unable to load query response from ' . $file]]
91 }
92 }
93 };
Akron8ea84292018-10-24 13:41:52 +020094
Akron6d49c1f2018-10-11 14:22:21 +020095 my $response = $file->slurp;
Akrona3c353c2019-02-14 23:50:00 +010096 my $decode = decode_json($response);
97 unless ($decode) {
98 return {
99 status => 500,
100 json => {
101 errors => [[0, 'Unable to parse JSON']]
102 }
103 }
104 };
105
106 return $decode;
Akron6d49c1f2018-10-11 14:22:21 +0200107};
108
Akron1a9d5be2020-03-19 17:28:33 +0100109app->defaults('oauth.client_list' => []);
Helge278fbca2022-11-29 18:49:15 +0100110app->defaults('oauth.plugin_list' => []);
Helgedb720ea2023-03-20 09:39:36 +0100111app->defaults('oauth.pluginin_list' => []);
Akron6d49c1f2018-10-11 14:22:21 +0200112
Akron0e1ed242018-10-11 13:22:00 +0200113# Base page
Akron63d963b2019-07-05 15:35:51 +0200114get '/v1.0/' => sub {
Akron6d49c1f2018-10-11 14:22:21 +0200115 shift->render(text => 'Fake server available');
Akron0e1ed242018-10-11 13:22:00 +0200116};
117
Akron32396632018-10-11 17:08:37 +0200118
Akrond00b4272020-02-05 17:00:33 +0100119get '/v1.0/redirect-target-a' => sub {
120 shift->render(text => 'Redirect Target!');
121} => 'redirect-target';
122
123
124# Base page
125get '/v1.0/redirect' => sub {
126 my $c = shift;
127 $c->res->code(308);
128 $c->res->headers->location($c->url_for('redirect-target')->to_abs);
129 return $c->render(text => '');
130};
131
132
Akron0e1ed242018-10-11 13:22:00 +0200133# Search fixtures
Akron63d963b2019-07-05 15:35:51 +0200134get '/v1.0/search' => sub {
Akron0e1ed242018-10-11 13:22:00 +0200135 my $c = shift;
136 my $v = $c->validation;
137 $v->optional('q');
138 $v->optional('page');
139 $v->optional('ql');
Akroncd42a142019-07-12 18:55:37 +0200140 $v->optional('cq');
Akron0e1ed242018-10-11 13:22:00 +0200141 $v->optional('count');
142 $v->optional('context');
Akron8ea84292018-10-24 13:41:52 +0200143 $v->optional('offset');
Akronc58bfc42020-10-05 12:09:45 +0200144 $v->optional('pipes');
Akron910828a2025-06-27 15:38:48 +0200145 $v->optional('response-pipes');
Akron2fc697a2024-06-28 10:35:10 +0200146 $v->optional('fields');
Akron8ea84292018-10-24 13:41:52 +0200147 $v->optional('cutoff')->in(qw/true false/);
Akron0e1ed242018-10-11 13:22:00 +0200148
Akron32396632018-10-11 17:08:37 +0200149 $c->app->log->debug('Receive request');
150
Akron0e1ed242018-10-11 13:22:00 +0200151 # Response q=x&ql=cosmas3
152 if ($v->param('ql') && $v->param('ql') eq 'cosmas3') {
153 return $c->render(
154 status => 400,
155 json => {
156 "\@context" => "http://korap.ids-mannheim.de/ns/koral/0.3/context.jsonld",
157 "errors" => [[307,"cosmas3 is not a supported query language!"]]
158 });
159 };
160
Akron6d49c1f2018-10-11 14:22:21 +0200161 if (!$v->param('q')) {
Akron8ea84292018-10-24 13:41:52 +0200162 return $c->render(%{$c->load_response('query_no_query')});
Akron0e1ed242018-10-11 13:22:00 +0200163 };
164
Akroncce055c2021-07-02 12:18:03 +0200165 if ($v->param('q') eq 'error') {
166 return $c->render(
167 status => 500,
168 inline => '<html><head>ERROR</head></html>'
169 );
170 };
171
Akron9b829012024-09-23 14:56:53 +0200172 if ($v->param('q') eq $Kalamar::Controller::Search::query_placeholder) {
173 # Get response based on query parameter
174 my $response = $c->load_response('query_baum_o0_c25_cq');
175 return $c->render(%$response);
176 };
177
Akron8ea84292018-10-24 13:41:52 +0200178 my @slug_base = ($v->param('q'));
179 push @slug_base, 'o' . $v->param('offset') if defined $v->param('offset');
180 push @slug_base, 'c' . $v->param('count') if defined $v->param('count');
181 push @slug_base, 'co' . $v->param('cutoff') if defined $v->param('cutoff');
Akroncd42a142019-07-12 18:55:37 +0200182 push @slug_base, 'cq' if defined $v->param('cq');
Akronc58bfc42020-10-05 12:09:45 +0200183 push @slug_base, 'p' . $v->param('pipes') if defined $v->param('pipes');
Akron910828a2025-06-27 15:38:48 +0200184 push @slug_base, 'rp' . $v->param('response-pipes') if defined $v->param('response-pipes');
Akron8ea84292018-10-24 13:41:52 +0200185
Akron2fc697a2024-06-28 10:35:10 +0200186 if (defined $v->param('fields') && ($v->param('fields') ne join(',', @default_search_fields))) {
187 push @slug_base, 'f' .join('-', split(',', $v->param('fields')));
188 };
189
Akron6d49c1f2018-10-11 14:22:21 +0200190 # Get response based on query parameter
Akron8ea84292018-10-24 13:41:52 +0200191 my $response = $c->load_response('query_' . slugify(join('_', @slug_base)));
Akron0e1ed242018-10-11 13:22:00 +0200192
193 # Check authentification
194 if (my $auth = $c->req->headers->header('Authorization')) {
Akron33f5c672019-06-24 19:40:47 +0200195
Akroncdfd9d52019-07-23 11:35:00 +0200196 $c->app->log->debug("There is an authorization header $auth");
Akron33f5c672019-06-24 19:40:47 +0200197 if ($auth =~ /^Bearer/) {
198 # Username unknown in OAuth2
199 $response->{json}->{meta}->{authorized} = 'yes';
Akron0e1ed242018-10-11 13:22:00 +0200200 };
Akroncdfd9d52019-07-23 11:35:00 +0200201
202 # Code is expired
203 if ($c->expired($auth)) {
204
205 $c->app->log->debug("The access token has expired");
206
207 return $c->render(
208 status => 401,
209 json => {
210 errors => [[2003, 'Access token is expired']]
211 }
212 );
213 }
214
215 # Auth token is invalid
216 if ($auth =~ /^Bearer inv4lid/) {
217 $c->app->log->debug("The access token is invalid");
218
219 return $c->render(
220 status => 401,
221 json => {
222 errors => [[2011, 'Access token is invalid']]
223 }
224 );
225 }
Akron0e1ed242018-10-11 13:22:00 +0200226 };
227
Akronc58bfc42020-10-05 12:09:45 +0200228 if ($v->param('pipes')) {
229 $response->{json}->{meta}->{pipes} = $v->param('pipes');
Akron7b9a1962020-07-02 09:52:53 +0200230 };
231
Akron910828a2025-06-27 15:38:48 +0200232 if ($v->param('response-pipes')) {
233 $response->{json}->{meta}->{responsePipes} = $v->param('response-pipes');
234 };
235
Akron6d49c1f2018-10-11 14:22:21 +0200236 # Set page parameter
Akron0e1ed242018-10-11 13:22:00 +0200237 if ($v->param('page')) {
Akron6d49c1f2018-10-11 14:22:21 +0200238 $response->{json}->{meta}->{startIndex} = $v->param("startIndex");
Akron0e1ed242018-10-11 13:22:00 +0200239 };
240
Akron0e1ed242018-10-11 13:22:00 +0200241 # Simple search fixture
Akron32396632018-10-11 17:08:37 +0200242 $c->render(%$response);
243
244 $c->app->log->debug('Rendered result');
245
246 return 1;
Akron0e1ed242018-10-11 13:22:00 +0200247};
248
Akron80a84b22018-10-24 17:44:24 +0200249# Textinfo fixtures
Akron4e413fb2023-09-26 13:11:11 +0200250get '/v1.0/corpus/#corpusId/#docId/#textId' => sub {
Akron80a84b22018-10-24 17:44:24 +0200251 my $c = shift;
Akron910828a2025-06-27 15:38:48 +0200252 my $v = $c->validation;
253 $v->optional('response-pipes');
Akron80a84b22018-10-24 17:44:24 +0200254
255 my $file = join('_', (
256 'textinfo',
257 $c->stash('corpusId'),
258 $c->stash('docId'),
259 $c->stash('textId')
260 ));
261
262 my $slug = slugify($file);
263
264 # Get response based on query parameter
265 my $response = $c->load_response($slug);
Akron910828a2025-06-27 15:38:48 +0200266
267 if ($v->param('response-pipes')) {
268 $response->{json}->{meta}->{responsePipes} = $v->param('response-pipes');
269 };
270
Akron80a84b22018-10-24 17:44:24 +0200271 return $c->render(%$response);
272};
273
Akron0e1ed242018-10-11 13:22:00 +0200274
Akronb80341d2018-10-15 19:46:23 +0200275# Matchinfo fixtures
Akron06d4d1f2024-06-05 11:59:20 +0200276get '/v1.0/corpus/#corpusId/#docId/#textId/#matchId' => sub {
Akronb80341d2018-10-15 19:46:23 +0200277 my $c = shift;
Akron910828a2025-06-27 15:38:48 +0200278 my $v = $c->validation;
279 $v->optional('response-pipes');
Akronb80341d2018-10-15 19:46:23 +0200280
281 my $file = join('_', (
282 'matchinfo',
283 $c->stash('corpusId'),
284 $c->stash('docId'),
285 $c->stash('textId'),
286 $c->stash('matchId')
287 ));
288
Akronb8d0b402018-10-18 23:51:52 +0200289 my $slug = slugify($file);
290
Akronb80341d2018-10-15 19:46:23 +0200291 # Get response based on query parameter
Akronb8d0b402018-10-18 23:51:52 +0200292 my $response = $c->load_response($slug);
Akron910828a2025-06-27 15:38:48 +0200293
294 if ($v->param('response-pipes')) {
295 $response->{json}->{meta}->{responsePipes} = $v->param('response-pipes');
296 };
297
Akronb80341d2018-10-15 19:46:23 +0200298 return $c->render(%$response);
299};
300
Akron0e1ed242018-10-11 13:22:00 +0200301
Akronbe61f4c2018-10-20 00:52:58 +0200302# Statistics endpoint
Akron63d963b2019-07-05 15:35:51 +0200303get '/v1.0/statistics' => sub {
Akronbe61f4c2018-10-20 00:52:58 +0200304 my $c = shift;
305 my $v = $c->validation;
Akron5fa61e92019-07-15 11:56:11 +0200306 $v->optional('cq');
Akron910828a2025-06-27 15:38:48 +0200307 $v->optional('response-pipes', 'trim');
Akronbe61f4c2018-10-20 00:52:58 +0200308
309 my @list = 'corpusinfo';
Akron5fa61e92019-07-15 11:56:11 +0200310 if ($v->param('cq')) {
311 push @list, $v->param('cq');
Akronbe61f4c2018-10-20 00:52:58 +0200312 };
313 my $slug = slugify(join('_', @list));
314
315 # Get response based on query parameter
316 my $response = $c->load_response($slug);
Akron910828a2025-06-27 15:38:48 +0200317
318 if ($v->param('response-pipes')) {
319 my $meta = $response->{json}->{meta} // {};
320 $meta->{responsePipes} = $v->param('response-pipes');
321 $response->{json}->{meta} = $meta;
322 };
323
Akronbe61f4c2018-10-20 00:52:58 +0200324 return $c->render(%$response);
325};
326
Akron0e1ed242018-10-11 13:22:00 +0200327############
328# Auth API #
329############
330
331# Request API token
Akron63d963b2019-07-05 15:35:51 +0200332get '/v1.0/auth/logout' => sub {
Akron0e1ed242018-10-11 13:22:00 +0200333 my $c = shift;
334
335 if (my $auth = $c->req->headers->header('Authorization')) {
Akroncdfd9d52019-07-23 11:35:00 +0200336
337 if ($auth =~ /^Bearer/) {
338 $c->app->log->debug('Server-Logout: ' . $auth);
339 return $c->render(json => { msg => [[0, 'Fine!']]});
Akron0e1ed242018-10-11 13:22:00 +0200340 };
341 };
342
343 return $c->render(status => 400, json => { error => [[0, 'No!']]});
344};
345
346
347# Request API token
Akron63d963b2019-07-05 15:35:51 +0200348get '/v1.0/auth/apiToken' => sub {
Akron0e1ed242018-10-11 13:22:00 +0200349 my $c = shift;
350
351 # Get auth header
352 my $auth = $c->req->headers->authorization;
353
354 # Authorization missing or not basic
355 if (!$auth || $auth !~ s/\s*Basic\s+//gi) {
356 return $c->render(
357 json => {
358 error => [[2, 'x']]
359 }
360 );
361 };
362
363 # Decode header
364 my ($username, $pwd) = @{b($auth)->b64_decode->split(':')->to_array};
365
366 # the password is 'pass'
367 if ($pwd) {
Akrona205b142022-11-28 13:35:19 +0100368 if ($pwd eq 'ldaperr') {
Akron3d673062019-01-29 15:54:16 +0100369 return $c->render(
370 format => 'html',
371 status => 401,
372 json => {
373 "errors" => [[2022,"LDAP Authentication failed due to unknown user or password!"]]
374 }
375 );
Akron0e1ed242018-10-11 13:22:00 +0200376 };
377
378 return $c->render(
379 json => {
380 error => [[2004, undef]]
381 }
382 );
383 };
384
385 return $c->render(
386 json => {
387 error => [[2004, undef]]
388 }
389 );
390};
391
Akron33f5c672019-06-24 19:40:47 +0200392
393# Request API token
Akron63d963b2019-07-05 15:35:51 +0200394post '/v1.0/oauth2/token' => sub {
Akron33f5c672019-06-24 19:40:47 +0200395 my $c = shift;
396
Akron63d963b2019-07-05 15:35:51 +0200397 my $grant_type = $c->param('grant_type') // 'undefined';
398
399 if ($grant_type eq 'password') {
Akron33f5c672019-06-24 19:40:47 +0200400
Akron8bbbecf2019-07-01 18:57:30 +0200401 # Check for wrong client id
402 if ($c->param('client_id') ne '2') {
403 return $c->render(
404 json => {
405 "error_description" => "Unknown client with " . $_->{client_id},
406 "error" => "invalid_client"
407 },
408 status => 401
409 );
410 }
Akron33f5c672019-06-24 19:40:47 +0200411
Akron8bbbecf2019-07-01 18:57:30 +0200412 # Check for wrong client secret
413 elsif ($c->param('client_secret') ne 'k414m4r-s3cr3t') {
414 return $c->render(
415 json => {
416 "error_description" => "Invalid client credentials",
417 "error" => "invalid_client"
418 },
419 status => 401
420 );
421 }
Akron33f5c672019-06-24 19:40:47 +0200422
Akron8bbbecf2019-07-01 18:57:30 +0200423 # Check for wrong user name
Akron6a228db2021-10-14 15:57:00 +0200424 elsif ($c->param('username') !~ /^t.st$/) {
Akron8bbbecf2019-07-01 18:57:30 +0200425 return $c->render(json => {
426 error => [[2004, undef]]
427 });
428 }
429
430 # Check for ldap error
431 elsif ($c->param('password') eq 'ldaperr') {
432 return $c->render(
433 format => 'html',
434 status => 401,
435 json => {
436 "errors" => [
437 [
438 2022,
439 "LDAP Authentication failed due to unknown user or password!"
440 ]
441 ]
442 }
443 );
444 }
445
446 # Check for wrong password
447 elsif ($c->param('password') ne 'pass') {
448 return $c->render(json => {
449 format => 'html',
450 status => 401,
Akron33f5c672019-06-24 19:40:47 +0200451 "errors" => [[2022,"LDAP Authentication failed due to unknown user or password!"]]
Akron8bbbecf2019-07-01 18:57:30 +0200452 });
453 }
454
455 # Return fine access
456 return $c->render(
457 json => {
Akroncdfd9d52019-07-23 11:35:00 +0200458 "access_token" => $c->get_token('access_token'),
459 "refresh_token" => $c->get_token('refresh_token'),
Akron8bbbecf2019-07-01 18:57:30 +0200460 "scope" => "all",
461 "token_type" => "Bearer",
462 "expires_in" => 86400
463 });
464 }
465
466 # Refresh token
Akron63d963b2019-07-05 15:35:51 +0200467 elsif ($grant_type eq 'refresh_token') {
Akroncdfd9d52019-07-23 11:35:00 +0200468
469 if ($c->param('refresh_token') eq 'inv4lid') {
470 return $c->render(
471 status => 400,
472 json => {
473 "error_description" => "Refresh token is expired",
474 "error" => "invalid_grant"
475 }
476 );
477 };
478
479 $c->app->log->debug("Refresh the token in the mock server!");
480
Akron8bbbecf2019-07-01 18:57:30 +0200481 return $c->render(
482 status => 200,
483 json => {
Akroncdfd9d52019-07-23 11:35:00 +0200484 "access_token" => $c->get_token("access_token_2"),
485 "refresh_token" => $c->get_token("refresh_token_2"),
Akron8bbbecf2019-07-01 18:57:30 +0200486 "token_type" => "Bearer",
487 "expires_in" => 86400
Akron33f5c672019-06-24 19:40:47 +0200488 }
489 );
490 }
491
Akron83209f72021-01-29 17:54:15 +0100492 # Get auth_token_1
493 elsif ($grant_type eq 'authorization_code') {
Akrone3daaeb2023-05-08 09:44:18 +0200494 if ($c->param('code') && $c->param('code') eq $tokens{auth_token_1}) {
Akron83209f72021-01-29 17:54:15 +0100495 return $c->render(
496 status => 200,
497 json => {
498 "access_token" => $tokens{access_token_3},
499 "expires_in" => 31536000,
500 "scope" => 'match_info search openid',
501 "token_type" => "Bearer"
502 }
503 );
504 };
505 }
506
Akron8bbbecf2019-07-01 18:57:30 +0200507 # Unknown token grant
508 else {
509 return $c->render(
Akron63d963b2019-07-05 15:35:51 +0200510 status => 400,
Akron8bbbecf2019-07-01 18:57:30 +0200511 json => {
512 "errors" => [
513 [
Akron63d963b2019-07-05 15:35:51 +0200514 0, "Grant Type unknown", $grant_type
Akron8bbbecf2019-07-01 18:57:30 +0200515 ]
516 ]
517 }
518 )
Akron33f5c672019-06-24 19:40:47 +0200519 }
Akron33f5c672019-06-24 19:40:47 +0200520};
521
Akron4cefe1f2019-09-04 10:11:28 +0200522# Revoke API token
523post '/v1.0/oauth2/revoke' => sub {
524 my $c = shift;
525
526 my $refresh_token = $c->param('token');
527
528 if ($c->param('client_secret') ne 'k414m4r-s3cr3t') {
529 return $c->render(
530 json => {
531 "error_description" => "Invalid client credentials",
532 "error" => "invalid_client"
533 },
534 status => 401
535 );
536 };
537
538 return $c->render(
539 text => ''
540 )
541};
Akron33f5c672019-06-24 19:40:47 +0200542
Akron59992122019-10-29 11:28:45 +0100543# Register a client
544post '/v1.0/oauth2/client/register' => sub {
545 my $c = shift;
546 my $json = $c->req->json;
547
Akrondc50c892021-05-05 18:12:02 +0200548 if ($json->{redirectURI}) {
549 return $c->render(
550 status => 400,
551 json => {
552 errors => [
553 [
554 201,
555 "Unrecognized field \"redirectURI\" (class de.ids_mannheim.korap.web.input.OAuth2ClientJson), not marked as ignorable (5 known properties: \"redirect_uri\", \"type\", \"name\", \"description\", \"url\"])\n at [Source: (org.eclipse.jetty.server.HttpInputOverHTTP); line: 1, column: 94] (through reference chain: de.ids_mannheim.korap.web.input.OAuth2ClientJson[\"redirectURI\"])"
556 ]
557 ]
558 }
559 );
560 };
561
Akron59992122019-10-29 11:28:45 +0100562 my $name = $json->{name};
Akron1a9d5be2020-03-19 17:28:33 +0100563 my $desc = $json->{description};
Akron59992122019-10-29 11:28:45 +0100564 my $type = $json->{type};
565 my $url = $json->{url};
Akron9f2ad342022-05-04 16:16:40 +0200566 my $src = $json->{source};
Akronb6b156e2022-03-31 14:57:49 +0200567 my $redirect_uri = $json->{redirect_uri};
Akron59992122019-10-29 11:28:45 +0100568
Akron1a9d5be2020-03-19 17:28:33 +0100569 my $list = $c->app->defaults('oauth.client_list');
570
Akron6b75d122022-05-12 17:39:05 +0200571 my $obj = {
Akrondc50c892021-05-05 18:12:02 +0200572 "client_id" => $tokens{new_client_id},
573 "client_name" => $name,
Akronbc94a9c2021-04-15 00:07:35 +0200574 "client_description" => $desc,
Akronb6b156e2022-03-31 14:57:49 +0200575 "client_url" => $url,
Akron9f2ad342022-05-04 16:16:40 +0200576 "client_redirect_uri" => $redirect_uri,
Akron6b75d122022-05-12 17:39:05 +0200577 "client_type" => $type
Akronb6b156e2022-03-31 14:57:49 +0200578 };
579
Akron6b75d122022-05-12 17:39:05 +0200580 # Plugin!
581 if ($src) {
582 $obj->{source} = $src;
583 $obj->{client_id} = $tokens{new_client_id_3};
584 };
585
586 push @$list, $obj;
587
Akronb6b156e2022-03-31 14:57:49 +0200588 if ($redirect_uri && $redirect_uri =~ /FAIL$/) {
589 return $c->render(
590 status => 400,
591 json => {
592 "error_description" => $redirect_uri . " is invalid.",
593 "error" => "invalid_request"
594 }
595 )
Akron1a9d5be2020-03-19 17:28:33 +0100596 };
597
Akron59992122019-10-29 11:28:45 +0100598 # Confidential server application
599 if ($type eq 'CONFIDENTIAL') {
Akronb6b156e2022-03-31 14:57:49 +0200600
Akron59992122019-10-29 11:28:45 +0100601 return $c->render(json => {
Akronb6b156e2022-03-31 14:57:49 +0200602 client_id => $tokens{new_client_id_2},
Akron59992122019-10-29 11:28:45 +0100603 client_secret => $tokens{new_client_secret}
604 });
605 };
606
607 # Desktop application
608 return $c->render(json => {
609 client_id => $tokens{new_client_id}
610 });
611};
612
Helgedb720ea2023-03-20 09:39:36 +0100613# Mock API list plugins
Helge278fbca2022-11-29 18:49:15 +0100614post '/v1.0/plugins' => sub {
Akron0f1b93b2020-03-17 11:37:19 +0100615 my $c = shift;
Akron276afc02021-06-14 11:00:21 +0200616 my $v = $c->validation;
Helgedb720ea2023-03-20 09:39:36 +0100617 $v->required('super_client_id');
618 $v->required('super_client_secret');
619 if ($v->has_error) {
620 return $c->render(
621 json => [],
622 status => 400
623 );
624 };
Akron276afc02021-06-14 11:00:21 +0200625
Helgedb720ea2023-03-20 09:39:36 +0100626 return $c->render(
627 json => $c->stash('oauth.plugin_list'),
628 status => 200
629 );
630};
631
632# Mock API list installed plugins
633post '/v1.0/plugins/installed' => sub {
634 my $c = shift;
635 my $v = $c->validation;
Akron276afc02021-06-14 11:00:21 +0200636 $v->required('super_client_id');
637 $v->required('super_client_secret');
638
639 if ($v->has_error) {
640 return $c->render(
641 json => [],
642 status => 400
643 );
644 };
Helge278fbca2022-11-29 18:49:15 +0100645
Helgedb720ea2023-03-20 09:39:36 +0100646 return $c->render(
647 json => $c->stash('oauth.pluginin_list'),
648 status => 200
649 );
650};
651
652
653# Mock API plugin installation
654post '/v1.0/plugins/install' => sub {
655 my $c = shift;
656 my $v = $c->validation;
657 $v->required('super_client_id');
658 $v->required('super_client_secret');
659 $v->required('client_id');
660 my $cl_id = $c->param('client_id');
661 if ($v->has_error) {
Helge278fbca2022-11-29 18:49:15 +0100662 return $c->render(
Helgedb720ea2023-03-20 09:39:36 +0100663 json => [],
664 status => 400
665 );
666 };
667
668 my $date = "2022-12-13T16:33:27.621+01:00[Europe/Berlin]";
669 my $pl_list = $c->app->defaults('oauth.plugin_list');
670 my $cl_name = (grep{($_->{client_id} eq $cl_id)}@$pl_list)[0]->{client_name};
671
672 if (length $cl_name){
673
674 my %inst_plugin = (
675 "name" => $cl_name,
676 "client_id" => $cl_id,
677 "installed_date" => $date,
678 );
679
680 $c->add_instplugin(\%inst_plugin);
681
682 return $c->render(
683 json => %inst_plugin,
Helge278fbca2022-11-29 18:49:15 +0100684 status => 200
685 );
686 }
Helge278fbca2022-11-29 18:49:15 +0100687
Helgedb720ea2023-03-20 09:39:36 +0100688 return $c->render(
689 json => [],
690 status => 400
691 );
Helge278fbca2022-11-29 18:49:15 +0100692};
693
Helged36478d2023-06-08 17:43:01 +0200694# Mock API plugin uninstallation
695post '/v1.0/plugins/uninstall' => sub {
696 my $c = shift;
697 my $v = $c->validation;
698 $v->required('super_client_id');
699 $v->required('super_client_secret');
700 $v->required('client_id');
701 if ($v->has_error) {
702 return $c->render(
703 json => [],
704 status => 400
705 );
706 };
707 my $cl_id = $c->param('client_id');
708
709 my $plin_list = $c->app->defaults('oauth.pluginin_list');
710 my @new_list = grep{!($_->{client_id} eq $cl_id)}@$plin_list;
711 $c->app->defaults('oauth.pluginin_list' => \@new_list);
712
713 if(scalar @new_list eq scalar @$plin_list){
714 return $c->render(
715 status => 404
716 );
717 }
718 return $c->render(
719 json => $c->stash('oauth.pluginin_list'),
720 status => 200
721 );
722 };
Helgedb720ea2023-03-20 09:39:36 +0100723
Helge278fbca2022-11-29 18:49:15 +0100724# Register a client
725post '/v1.0/oauth2/client/list' => sub {
726 my $c = shift;
727
728 my $v = $c->validation;
729 $v->required('super_client_id');
730 $v->required('super_client_secret');
731
Helge05436702024-08-05 17:08:44 +0200732 $v->optional('filter_by' );
733 $v->optional('authorized_only' );
734
Helge278fbca2022-11-29 18:49:15 +0100735 if ($v->has_error) {
736 return $c->render(
737 json => [],
738 status => 400
739 );
740 };
741
Akron276afc02021-06-14 11:00:21 +0200742
Akron0f1b93b2020-03-17 11:37:19 +0100743 # $c->param('client_secret');
Akron1a9d5be2020-03-19 17:28:33 +0100744
745 # Is empty [] when nothing registered
746
Akron0f1b93b2020-03-17 11:37:19 +0100747 return $c->render(
Akron1a9d5be2020-03-19 17:28:33 +0100748 json => $c->stash('oauth.client_list'),
749 status => 200
750 );
751};
752
Akrondb1f4672023-01-24 12:05:07 +0100753# Get client info
754post '/v1.0/oauth2/client/:client_id' => sub {
755 my $c = shift;
756
757 # Validate input
758 my $v = $c->validation;
759 $v->required('super_client_id');
760 $v->required('super_client_secret');
761
762 if ($v->has_error) {
763 return $c->render(
764 status => 400,
765 json => {
766 error_description => "No super client",
767 error => "no_superclient"
768 }
769 );
770 };
771
772 my $client_id = $c->stash('client_id');
773
774 my $list = $c->stash('oauth.client_list');
775
776 foreach (@$list) {
777 if ($_->{client_id} eq $client_id) {
778 return $c->render(
779 json => $_,
780 status => 200
781 );
782 };
783 };
784
785 return $c->render(
786 json => {
787 error_description => "Unknown client with $client_id.",
788 error => "invalid_client"
789 },
790 status => 401
791 );
792};
793
Akronbc94a9c2021-04-15 00:07:35 +0200794
795# Get token list
796post '/v1.0/oauth2/token/list' => sub {
797 my $c = shift;
798 return $c->render(json => [
799 {
800 "client_description" => "Nur ein Beispiel",
801 "client_id" => $tokens{new_client_id},
802 "client_name" => "Beispiel",
803 "client_url" => "",
804 "created_date" => "2021-04-14T19:40:26.742+02:00[Europe\/Berlin]",
805 "expires_in" => "31533851",
806 "scope" => [
807 "match_info",
808 "search",
809 "openid"
810 ],
811 "token" => "jhkhkjhk_hjgjsfz67i",
812 "user_authentication_time" => "2021-04-14T19:39:41.81+02:00[Europe\/Berlin]"
813 }
814 ]);
815};
816
Akron1a9d5be2020-03-19 17:28:33 +0100817del '/v1.0/oauth2/client/deregister/:client_id' => sub {
818 my $c = shift;
819 my $client_id = $c->stash('client_id');
820
821 my $list = $c->app->defaults('oauth.client_list');
822
823 my $break = -1;
824 for (my $i = 0; $i < @$list; $i++) {
Akrondc50c892021-05-05 18:12:02 +0200825 if ($list->[$i]->{client_id} eq $client_id) {
Akron1a9d5be2020-03-19 17:28:33 +0100826 $break = $i;
827 last;
828 };
829 };
830
831 if ($break != -1) {
832 splice @$list, $break, 1;
833 }
834
835 else {
836 return $c->render(
837 json => {
838 error_description => "Unknown client with $client_id.",
839 error => "invalid_client"
Akron0f1b93b2020-03-17 11:37:19 +0100840 },
Akron1a9d5be2020-03-19 17:28:33 +0100841 status => 401
842 );
843 };
844
845 return $c->render(
846 json => $c->stash('oauth.client_list'),
Akron0f1b93b2020-03-17 11:37:19 +0100847 status => 200
848 );
849};
850
Akron83209f72021-01-29 17:54:15 +0100851post '/v1.0/oauth2/authorize' => sub {
852 my $c = shift;
853 my $type = $c->param('response_type');
854 my $client_id = $c->param('client_id');
Akrona8efaa92022-04-09 14:45:43 +0200855 my $scope = $c->param('scope');
856 my $state = $c->param('state');
857 my $redirect_uri = $c->param('redirect_uri') // 'NO';
Akron83209f72021-01-29 17:54:15 +0100858
Akrona8efaa92022-04-09 14:45:43 +0200859 if ($type eq 'code' && $client_id eq 'xyz') {
860
861 if ($state eq 'fail') {
862 $c->res->headers->location(
863 Mojo::URL->new($redirect_uri)->query({
864 error_description => 'FAIL'
865 })
866 );
867 $c->res->code(400);
868 return $c->rendered;
869 };
870
Akron9ccf69a2023-01-31 14:21:37 +0100871 if (index($redirect_uri,'http://wrong') >= 0) {
872 return $c->render(
873 code => 400,
874 content_type => 'text/plain',
875 text => '{"error_description":"Invalid redirect URI","state":"ZMwDGTZ2RY","error":"invalid_request"}'
876 );
877 };
878
Akrona8efaa92022-04-09 14:45:43 +0200879 return $c->redirect_to(
880 Mojo::URL->new($redirect_uri)->query({
881 code => $tokens{auth_token_1},
882 scope => $scope,
883 })
884 );
885 }
886
887 elsif ($type eq 'code') {
Akrone3daaeb2023-05-08 09:44:18 +0200888 my $loc = Mojo::URL->new($redirect_uri)->query({
889 code => $tokens{auth_token_1},
890 scope => 'match_info search openid'
891 });
Akron83209f72021-01-29 17:54:15 +0100892
Akrone3daaeb2023-05-08 09:44:18 +0200893 my $res = $c->res;
894 $res->headers->location($loc);
895 return $c->rendered($client_id eq '307' ? 307 : 302);
896 # return $c->rendered(302);
Akron9ccf69a2023-01-31 14:21:37 +0100897 };
898
899 return $c->render(
900 code => 400,
901 content_type => 'text/plain',
902 content => 'Unknown'
903 );
Akron83209f72021-01-29 17:54:15 +0100904};
905
Akron0f1b93b2020-03-17 11:37:19 +0100906
Akronabdf9a92021-01-12 19:06:57 +0100907#######################
908# Query Reference API #
909#######################
910
911use CHI;
912my $chi = CHI->new(
913 driver => 'Memory',
914 global => 1
915);
916
917# Store query
918put '/v1.0/query/~:user/:query_name' => sub {
919 my $c = shift;
920 my $user = $c->stash('user');
921 my $qname = $c->stash('query_name');
922
923 if ($chi->is_valid($qname)) {
924 return $c->render(
925 json => {
926 errors => [
927 {
928 message => 'Unable to store query reference'
929 }
930 ]
931 }, status => 400
932 );
933 };
934
935 my $json = $c->req->json;
936
937 my $store = {
938 name => $qname,
939 koralQuery => { '@type' => 'Okay' },
940 query => $json->{query},
941 queryType => $json->{queryType},
942 type => $json->{type},
943 queryLanguage => $json->{queryLanguage},
944 };
945
946 if (exists $json->{description}) {
947 $store->{description} = $json->{description}
948 };
949
950 # Set query reference
951 $chi->set($qname => $store);
952
953 my $queries = $chi->get('~queries') // [];
954 push @$queries, $qname;
955 $chi->set('~queries' => $queries);
956
957 return $c->render(
958 status => 201,
959 text => ''
960 );
961};
962
963# Get query
964get '/v1.0/query/~:user/:query_name' => sub {
965 my $c = shift;
966
967 my $user = $c->stash('user');
968 my $qname = $c->stash('query_name');
969
970 my $json = $chi->get($qname);
971
972 if ($json) {
973 return $c->render(
974 json => $json
975 );
976 };
977
978 return $c->render(
979 json => {
980 errors => [
981 {
982 message => 'Query reference not found'
983 }
984 ]
985 }, status => 404
986 );
987};
988
989
990# Get all queries
991get '/v1.0/query/~:user' => sub {
992 my $c = shift;
993 my $user = $c->stash('user');
994 my $qs = $chi->get('~queries') // [];
995 my @queries = ();
996 foreach (@$qs) {
997 push @queries, $chi->get($_);
998 };
999 return $c->render(json => { refs => \@queries });
1000};
1001
1002
1003# Store query
1004del '/v1.0/query/~:user/:query_name' => sub {
1005 my $c = shift;
1006 my $user = $c->stash('user');
1007 my $qname = $c->stash('query_name');
1008
1009 $chi->remove($qname);
1010
1011 my $queries = $chi->get('~queries') // [];
1012
1013 my @clean = ();
1014 foreach (@$queries) {
1015 push @clean, $_ unless $_ eq $qname
1016 };
1017
1018 $chi->set('~queries' => \@clean);
1019
1020 return $c->render(
1021 status => 200,
1022 text => ''
1023 );
1024};
1025
Akronc1aaf932021-06-09 12:19:15 +02001026post '/v1.0/oauth2/revoke/super' => sub {
1027 my $c = shift;
1028
1029 my $s_client_id = $c->param('super_client_id');
1030 my $s_client_secret = $c->param('super_client_secret');
1031 my $token = $c->param('token');
1032
1033 return $c->render(text => 'SUCCESS');
1034};
1035
Akrona8efaa92022-04-09 14:45:43 +02001036get '/fakeclient/return' => sub {
1037 my $c = shift;
1038 $c->render(
1039 text => 'welcome back! [' . $c->param('code') . ']'
1040 );
1041} => 'return_uri';
1042
Akron0f1b93b2020-03-17 11:37:19 +01001043
Akron0e1ed242018-10-11 13:22:00 +02001044app->start;
1045
1046
1047__END__
1048
1049
1050 # Temporary:
1051 my $collection_query = {
1052 '@type' => "koral:docGroup",
1053 "operation" => "operation:or",
1054 "operands" => [
1055 {
1056 '@type' => "koral:docGroup",
1057 "operation" => "operation:and",
1058 "operands" => [
1059 {
1060 '@type' => "koral:doc",
1061 "key" => "title",
1062 "match" => "match:eq",
1063 "value" => "Der Birnbaum",
1064 "type" => "type:string"
1065 },
1066 {
1067 '@type' => "koral:doc",
1068 "key" => "pubPlace",
1069 "match" => "match:eq",
1070 "value" => "Mannheim",
1071 "type" => "type:string"
1072 },
1073 {
1074 '@type' => "koral:docGroup",
1075 "operation" => "operation:or",
1076 "operands" => [
1077 {
1078 '@type' => "koral:doc",
1079 "key" => "subTitle",
1080 "match" => "match:eq",
1081 "value" => "Aufzucht oder Pflege",
1082 "type" => "type:string"
1083 },
1084 {
1085 '@type' => "koral:doc",
1086 "key" => "subTitle",
1087 "match" => "match:eq",
1088 "value" => "Gedichte",
1089 "type" => "type:string"
1090 }
1091 ]
1092 }
1093 ]
1094 },
1095 {
1096 '@type' => "koral:doc",
1097 "key" => "pubDate",
1098 "match" => "match:geq",
1099 "value" => "2015-03-05",
1100 "type" => "type:date"
1101 }
1102 ]
1103 };