blob: 961c6ea282c3855c30a7bef80fbec62d62b2e385 [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;
8use Mojo::JWT;
Akron6d49c1f2018-10-11 14:22:21 +02009use Mojo::File qw/path/;
10use Mojo::Util qw/slugify/;
Akron0e1ed242018-10-11 13:22:00 +020011
12# This is an API fake server with fixtures
13
14my $secret = 's3cr3t';
Akron73f36082018-10-25 15:34:59 +020015my $fixture_path = path(Mojo::File->new(__FILE__)->dirname)->child('..', 'fixtures');
Akron0e1ed242018-10-11 13:22:00 +020016
Akron33f5c672019-06-24 19:40:47 +020017# Legacy:
Akron0e1ed242018-10-11 13:22:00 +020018helper jwt_encode => sub {
19 shift;
20 return Mojo::JWT->new(
21 secret => $secret,
22 token_type => 'api_token',
23 expires => time + (3 * 34 * 60 * 60),
24 claims => { @_ }
25 );
26};
27
Akron33f5c672019-06-24 19:40:47 +020028# Legacy;
Akron0e1ed242018-10-11 13:22:00 +020029helper jwt_decode => sub {
30 my ($c, $auth) = @_;
31 $auth =~ s/\s*api_token\s+//;
32 return Mojo::JWT->new(secret => $secret)->decode($auth);
33};
34
35
Akron6d49c1f2018-10-11 14:22:21 +020036# Load fixture responses
37helper 'load_response' => sub {
38 my $c = shift;
39 my $q_name = shift;
40 my $file = $fixture_path->child("response_$q_name.json");
Akron8ea84292018-10-24 13:41:52 +020041 $c->app->log->debug("Load response from $file");
42
Akron6d49c1f2018-10-11 14:22:21 +020043 unless (-f $file) {
44 return {
45 status => 500,
46 json => {
47 errors => [[0, 'Unable to load query response from ' . $file]]
48 }
49 }
50 };
Akron8ea84292018-10-24 13:41:52 +020051
Akron6d49c1f2018-10-11 14:22:21 +020052 my $response = $file->slurp;
Akrona3c353c2019-02-14 23:50:00 +010053 my $decode = decode_json($response);
54 unless ($decode) {
55 return {
56 status => 500,
57 json => {
58 errors => [[0, 'Unable to parse JSON']]
59 }
60 }
61 };
62
63 return $decode;
Akron6d49c1f2018-10-11 14:22:21 +020064};
65
66
Akron0e1ed242018-10-11 13:22:00 +020067# Base page
Akron63d963b2019-07-05 15:35:51 +020068get '/v1.0/' => sub {
Akron6d49c1f2018-10-11 14:22:21 +020069 shift->render(text => 'Fake server available');
Akron0e1ed242018-10-11 13:22:00 +020070};
71
Akron32396632018-10-11 17:08:37 +020072
Akron0e1ed242018-10-11 13:22:00 +020073# Search fixtures
Akron63d963b2019-07-05 15:35:51 +020074get '/v1.0/search' => sub {
Akron0e1ed242018-10-11 13:22:00 +020075 my $c = shift;
76 my $v = $c->validation;
77 $v->optional('q');
78 $v->optional('page');
79 $v->optional('ql');
Akroncd42a142019-07-12 18:55:37 +020080 $v->optional('cq');
Akron0e1ed242018-10-11 13:22:00 +020081 $v->optional('count');
82 $v->optional('context');
Akron8ea84292018-10-24 13:41:52 +020083 $v->optional('offset');
84 $v->optional('cutoff')->in(qw/true false/);
Akron0e1ed242018-10-11 13:22:00 +020085
Akron32396632018-10-11 17:08:37 +020086 $c->app->log->debug('Receive request');
87
Akron0e1ed242018-10-11 13:22:00 +020088 # Response q=x&ql=cosmas3
89 if ($v->param('ql') && $v->param('ql') eq 'cosmas3') {
90 return $c->render(
91 status => 400,
92 json => {
93 "\@context" => "http://korap.ids-mannheim.de/ns/koral/0.3/context.jsonld",
94 "errors" => [[307,"cosmas3 is not a supported query language!"]]
95 });
96 };
97
Akron6d49c1f2018-10-11 14:22:21 +020098 if (!$v->param('q')) {
Akron8ea84292018-10-24 13:41:52 +020099 return $c->render(%{$c->load_response('query_no_query')});
Akron0e1ed242018-10-11 13:22:00 +0200100 };
101
Akron8ea84292018-10-24 13:41:52 +0200102 my @slug_base = ($v->param('q'));
103 push @slug_base, 'o' . $v->param('offset') if defined $v->param('offset');
104 push @slug_base, 'c' . $v->param('count') if defined $v->param('count');
105 push @slug_base, 'co' . $v->param('cutoff') if defined $v->param('cutoff');
Akroncd42a142019-07-12 18:55:37 +0200106 push @slug_base, 'cq' if defined $v->param('cq');
Akron8ea84292018-10-24 13:41:52 +0200107
Akron6d49c1f2018-10-11 14:22:21 +0200108 # Get response based on query parameter
Akron8ea84292018-10-24 13:41:52 +0200109 my $response = $c->load_response('query_' . slugify(join('_', @slug_base)));
Akron0e1ed242018-10-11 13:22:00 +0200110
111 # Check authentification
112 if (my $auth = $c->req->headers->header('Authorization')) {
Akron33f5c672019-06-24 19:40:47 +0200113
114 my $jwt;
115 if ($auth =~ /^Bearer/) {
116 # Username unknown in OAuth2
117 $response->{json}->{meta}->{authorized} = 'yes';
118 }
119 elsif ($jwt = $c->jwt_decode($auth)) {
Akron6d49c1f2018-10-11 14:22:21 +0200120 $response->{json}->{meta}->{authorized} = $jwt->{username} if $jwt->{username};
Akron0e1ed242018-10-11 13:22:00 +0200121 };
122 };
123
Akron6d49c1f2018-10-11 14:22:21 +0200124 # Set page parameter
Akron0e1ed242018-10-11 13:22:00 +0200125 if ($v->param('page')) {
Akron6d49c1f2018-10-11 14:22:21 +0200126 $response->{json}->{meta}->{startIndex} = $v->param("startIndex");
Akron0e1ed242018-10-11 13:22:00 +0200127 };
128
Akron0e1ed242018-10-11 13:22:00 +0200129 # Simple search fixture
Akron32396632018-10-11 17:08:37 +0200130 $c->render(%$response);
131
132 $c->app->log->debug('Rendered result');
133
134 return 1;
Akron0e1ed242018-10-11 13:22:00 +0200135};
136
Akron80a84b22018-10-24 17:44:24 +0200137# Textinfo fixtures
Akron63d963b2019-07-05 15:35:51 +0200138get '/v1.0/corpus/:corpusId/:docId/:textId' => sub {
Akron80a84b22018-10-24 17:44:24 +0200139 my $c = shift;
140
141 my $file = join('_', (
142 'textinfo',
143 $c->stash('corpusId'),
144 $c->stash('docId'),
145 $c->stash('textId')
146 ));
147
148 my $slug = slugify($file);
149
150 # Get response based on query parameter
151 my $response = $c->load_response($slug);
152 return $c->render(%$response);
153};
154
Akron0e1ed242018-10-11 13:22:00 +0200155
Akronb80341d2018-10-15 19:46:23 +0200156# Matchinfo fixtures
Akron63d963b2019-07-05 15:35:51 +0200157get '/v1.0/corpus/:corpusId/:docId/:textId/:matchId/matchInfo' => sub {
Akronb80341d2018-10-15 19:46:23 +0200158 my $c = shift;
159
160 my $file = join('_', (
161 'matchinfo',
162 $c->stash('corpusId'),
163 $c->stash('docId'),
164 $c->stash('textId'),
165 $c->stash('matchId')
166 ));
167
Akronb8d0b402018-10-18 23:51:52 +0200168 my $slug = slugify($file);
169
Akronb80341d2018-10-15 19:46:23 +0200170 # Get response based on query parameter
Akronb8d0b402018-10-18 23:51:52 +0200171 my $response = $c->load_response($slug);
Akronb80341d2018-10-15 19:46:23 +0200172 return $c->render(%$response);
173};
174
Akron0e1ed242018-10-11 13:22:00 +0200175
Akronbe61f4c2018-10-20 00:52:58 +0200176# Statistics endpoint
Akron63d963b2019-07-05 15:35:51 +0200177get '/v1.0/statistics' => sub {
Akronbe61f4c2018-10-20 00:52:58 +0200178 my $c = shift;
179 my $v = $c->validation;
Akron5fa61e92019-07-15 11:56:11 +0200180 $v->optional('cq');
Akronbe61f4c2018-10-20 00:52:58 +0200181
182 my @list = 'corpusinfo';
Akron5fa61e92019-07-15 11:56:11 +0200183 if ($v->param('cq')) {
184 push @list, $v->param('cq');
Akronbe61f4c2018-10-20 00:52:58 +0200185 };
186 my $slug = slugify(join('_', @list));
187
188 # Get response based on query parameter
189 my $response = $c->load_response($slug);
190 return $c->render(%$response);
191};
192
Akron0e1ed242018-10-11 13:22:00 +0200193############
194# Auth API #
195############
196
197# Request API token
Akron63d963b2019-07-05 15:35:51 +0200198get '/v1.0/auth/logout' => sub {
Akron0e1ed242018-10-11 13:22:00 +0200199 my $c = shift;
200
201 if (my $auth = $c->req->headers->header('Authorization')) {
202 if (my $jwt = $c->jwt_decode($auth)) {
203 my $user = $jwt->{username} if $jwt->{username};
204
205 $c->app->log->debug('Server-Logout: ' . $user);
206 return $c->render(json => { msg => [[0, 'Fine!']]});
207 };
208 };
209
210 return $c->render(status => 400, json => { error => [[0, 'No!']]});
211};
212
213
214# Request API token
Akron63d963b2019-07-05 15:35:51 +0200215get '/v1.0/auth/apiToken' => sub {
Akron0e1ed242018-10-11 13:22:00 +0200216 my $c = shift;
217
218 # Get auth header
219 my $auth = $c->req->headers->authorization;
220
221 # Authorization missing or not basic
222 if (!$auth || $auth !~ s/\s*Basic\s+//gi) {
223 return $c->render(
224 json => {
225 error => [[2, 'x']]
226 }
227 );
228 };
229
230 # Decode header
231 my ($username, $pwd) = @{b($auth)->b64_decode->split(':')->to_array};
232
233 # the password is 'pass'
234 if ($pwd) {
235
236 # the password is 'pass'
237 if ($pwd eq 'pass') {
238
239 # Render info with token
240 my $jwt = $c->jwt_encode(username => $username);
241
242 # Render in the Kustvakt fashion:
243 return $c->render(
244 format => 'html',
245 text => encode_json({
246 %{$jwt->claims},
247 expires => $jwt->expires,
248 token => $jwt->encode,
249 token_type => 'api_token'
250 })
251 );
Akron3d673062019-01-29 15:54:16 +0100252 }
253
254 elsif ($pwd eq 'ldaperr') {
255 return $c->render(
256 format => 'html',
257 status => 401,
258 json => {
259 "errors" => [[2022,"LDAP Authentication failed due to unknown user or password!"]]
260 }
261 );
Akron0e1ed242018-10-11 13:22:00 +0200262 };
263
264 return $c->render(
265 json => {
266 error => [[2004, undef]]
267 }
268 );
269 };
270
271 return $c->render(
272 json => {
273 error => [[2004, undef]]
274 }
275 );
276};
277
Akron33f5c672019-06-24 19:40:47 +0200278
279# Request API token
Akron63d963b2019-07-05 15:35:51 +0200280post '/v1.0/oauth2/token' => sub {
Akron33f5c672019-06-24 19:40:47 +0200281 my $c = shift;
282
Akron63d963b2019-07-05 15:35:51 +0200283 my $grant_type = $c->param('grant_type') // 'undefined';
284
285 if ($grant_type eq 'password') {
Akron33f5c672019-06-24 19:40:47 +0200286
Akron8bbbecf2019-07-01 18:57:30 +0200287 # Check for wrong client id
288 if ($c->param('client_id') ne '2') {
289 return $c->render(
290 json => {
291 "error_description" => "Unknown client with " . $_->{client_id},
292 "error" => "invalid_client"
293 },
294 status => 401
295 );
296 }
Akron33f5c672019-06-24 19:40:47 +0200297
Akron8bbbecf2019-07-01 18:57:30 +0200298 # Check for wrong client secret
299 elsif ($c->param('client_secret') ne 'k414m4r-s3cr3t') {
300 return $c->render(
301 json => {
302 "error_description" => "Invalid client credentials",
303 "error" => "invalid_client"
304 },
305 status => 401
306 );
307 }
Akron33f5c672019-06-24 19:40:47 +0200308
Akron8bbbecf2019-07-01 18:57:30 +0200309 # Check for wrong user name
310 elsif ($c->param('username') ne 'test') {
311 return $c->render(json => {
312 error => [[2004, undef]]
313 });
314 }
315
316 # Check for ldap error
317 elsif ($c->param('password') eq 'ldaperr') {
318 return $c->render(
319 format => 'html',
320 status => 401,
321 json => {
322 "errors" => [
323 [
324 2022,
325 "LDAP Authentication failed due to unknown user or password!"
326 ]
327 ]
328 }
329 );
330 }
331
332 # Check for wrong password
333 elsif ($c->param('password') ne 'pass') {
334 return $c->render(json => {
335 format => 'html',
336 status => 401,
Akron33f5c672019-06-24 19:40:47 +0200337 "errors" => [[2022,"LDAP Authentication failed due to unknown user or password!"]]
Akron8bbbecf2019-07-01 18:57:30 +0200338 });
339 }
340
341 # Return fine access
342 return $c->render(
343 json => {
344 "access_token" => "4dcf8784ccfd26fac9bdb82778fe60e2",
345 "refresh_token" => "hlWci75xb8atDiq3924NUSvOdtAh7Nlf9z",
346 "scope" => "all",
347 "token_type" => "Bearer",
348 "expires_in" => 86400
349 });
350 }
351
352 # Refresh token
Akron63d963b2019-07-05 15:35:51 +0200353 elsif ($grant_type eq 'refresh_token') {
Akron8bbbecf2019-07-01 18:57:30 +0200354 return $c->render(
355 status => 200,
356 json => {
357 "access_token" => "abcde",
358 "refresh_token" => "fghijk",
359 "token_type" => "Bearer",
360 "expires_in" => 86400
Akron33f5c672019-06-24 19:40:47 +0200361 }
362 );
363 }
364
Akron8bbbecf2019-07-01 18:57:30 +0200365 # Unknown token grant
366 else {
367 return $c->render(
Akron63d963b2019-07-05 15:35:51 +0200368 status => 400,
Akron8bbbecf2019-07-01 18:57:30 +0200369 json => {
370 "errors" => [
371 [
Akron63d963b2019-07-05 15:35:51 +0200372 0, "Grant Type unknown", $grant_type
Akron8bbbecf2019-07-01 18:57:30 +0200373 ]
374 ]
375 }
376 )
Akron33f5c672019-06-24 19:40:47 +0200377 }
Akron33f5c672019-06-24 19:40:47 +0200378};
379
380
381
Akron0e1ed242018-10-11 13:22:00 +0200382app->start;
383
384
385__END__
386
387
388 # Temporary:
389 my $collection_query = {
390 '@type' => "koral:docGroup",
391 "operation" => "operation:or",
392 "operands" => [
393 {
394 '@type' => "koral:docGroup",
395 "operation" => "operation:and",
396 "operands" => [
397 {
398 '@type' => "koral:doc",
399 "key" => "title",
400 "match" => "match:eq",
401 "value" => "Der Birnbaum",
402 "type" => "type:string"
403 },
404 {
405 '@type' => "koral:doc",
406 "key" => "pubPlace",
407 "match" => "match:eq",
408 "value" => "Mannheim",
409 "type" => "type:string"
410 },
411 {
412 '@type' => "koral:docGroup",
413 "operation" => "operation:or",
414 "operands" => [
415 {
416 '@type' => "koral:doc",
417 "key" => "subTitle",
418 "match" => "match:eq",
419 "value" => "Aufzucht oder Pflege",
420 "type" => "type:string"
421 },
422 {
423 '@type' => "koral:doc",
424 "key" => "subTitle",
425 "match" => "match:eq",
426 "value" => "Gedichte",
427 "type" => "type:string"
428 }
429 ]
430 }
431 ]
432 },
433 {
434 '@type' => "koral:doc",
435 "key" => "pubDate",
436 "match" => "match:geq",
437 "value" => "2015-03-05",
438 "type" => "type:date"
439 }
440 ]
441 };