blob: 2c7f43b72a607409a91f43ebcefefa502d0e5e4e [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');
80 $v->optional('count');
81 $v->optional('context');
Akron8ea84292018-10-24 13:41:52 +020082 $v->optional('offset');
83 $v->optional('cutoff')->in(qw/true false/);
Akron0e1ed242018-10-11 13:22:00 +020084
Akron32396632018-10-11 17:08:37 +020085 $c->app->log->debug('Receive request');
86
Akron0e1ed242018-10-11 13:22:00 +020087 # Response q=x&ql=cosmas3
88 if ($v->param('ql') && $v->param('ql') eq 'cosmas3') {
89 return $c->render(
90 status => 400,
91 json => {
92 "\@context" => "http://korap.ids-mannheim.de/ns/koral/0.3/context.jsonld",
93 "errors" => [[307,"cosmas3 is not a supported query language!"]]
94 });
95 };
96
Akron6d49c1f2018-10-11 14:22:21 +020097 if (!$v->param('q')) {
Akron8ea84292018-10-24 13:41:52 +020098 return $c->render(%{$c->load_response('query_no_query')});
Akron0e1ed242018-10-11 13:22:00 +020099 };
100
Akron8ea84292018-10-24 13:41:52 +0200101 my @slug_base = ($v->param('q'));
102 push @slug_base, 'o' . $v->param('offset') if defined $v->param('offset');
103 push @slug_base, 'c' . $v->param('count') if defined $v->param('count');
104 push @slug_base, 'co' . $v->param('cutoff') if defined $v->param('cutoff');
105
Akron6d49c1f2018-10-11 14:22:21 +0200106 # Get response based on query parameter
Akron8ea84292018-10-24 13:41:52 +0200107 my $response = $c->load_response('query_' . slugify(join('_', @slug_base)));
Akron0e1ed242018-10-11 13:22:00 +0200108
109 # Check authentification
110 if (my $auth = $c->req->headers->header('Authorization')) {
Akron33f5c672019-06-24 19:40:47 +0200111
112 my $jwt;
113 if ($auth =~ /^Bearer/) {
114 # Username unknown in OAuth2
115 $response->{json}->{meta}->{authorized} = 'yes';
116 }
117 elsif ($jwt = $c->jwt_decode($auth)) {
Akron6d49c1f2018-10-11 14:22:21 +0200118 $response->{json}->{meta}->{authorized} = $jwt->{username} if $jwt->{username};
Akron0e1ed242018-10-11 13:22:00 +0200119 };
120 };
121
Akron6d49c1f2018-10-11 14:22:21 +0200122 # Set page parameter
Akron0e1ed242018-10-11 13:22:00 +0200123 if ($v->param('page')) {
Akron6d49c1f2018-10-11 14:22:21 +0200124 $response->{json}->{meta}->{startIndex} = $v->param("startIndex");
Akron0e1ed242018-10-11 13:22:00 +0200125 };
126
Akron0e1ed242018-10-11 13:22:00 +0200127 # Simple search fixture
Akron32396632018-10-11 17:08:37 +0200128 $c->render(%$response);
129
130 $c->app->log->debug('Rendered result');
131
132 return 1;
Akron0e1ed242018-10-11 13:22:00 +0200133};
134
Akron80a84b22018-10-24 17:44:24 +0200135# Textinfo fixtures
Akron63d963b2019-07-05 15:35:51 +0200136get '/v1.0/corpus/:corpusId/:docId/:textId' => sub {
Akron80a84b22018-10-24 17:44:24 +0200137 my $c = shift;
138
139 my $file = join('_', (
140 'textinfo',
141 $c->stash('corpusId'),
142 $c->stash('docId'),
143 $c->stash('textId')
144 ));
145
146 my $slug = slugify($file);
147
148 # Get response based on query parameter
149 my $response = $c->load_response($slug);
150 return $c->render(%$response);
151};
152
Akron0e1ed242018-10-11 13:22:00 +0200153
Akronb80341d2018-10-15 19:46:23 +0200154# Matchinfo fixtures
Akron63d963b2019-07-05 15:35:51 +0200155get '/v1.0/corpus/:corpusId/:docId/:textId/:matchId/matchInfo' => sub {
Akronb80341d2018-10-15 19:46:23 +0200156 my $c = shift;
157
158 my $file = join('_', (
159 'matchinfo',
160 $c->stash('corpusId'),
161 $c->stash('docId'),
162 $c->stash('textId'),
163 $c->stash('matchId')
164 ));
165
Akronb8d0b402018-10-18 23:51:52 +0200166 my $slug = slugify($file);
167
Akronb80341d2018-10-15 19:46:23 +0200168 # Get response based on query parameter
Akronb8d0b402018-10-18 23:51:52 +0200169 my $response = $c->load_response($slug);
Akronb80341d2018-10-15 19:46:23 +0200170 return $c->render(%$response);
171};
172
Akron0e1ed242018-10-11 13:22:00 +0200173
Akronbe61f4c2018-10-20 00:52:58 +0200174# Statistics endpoint
Akron63d963b2019-07-05 15:35:51 +0200175get '/v1.0/statistics' => sub {
Akronbe61f4c2018-10-20 00:52:58 +0200176 my $c = shift;
177 my $v = $c->validation;
178 $v->optional('corpusQuery');
179
180 my @list = 'corpusinfo';
181 if ($v->param('corpusQuery')) {
182 push @list, $v->param('corpusQuery');
183 };
184 my $slug = slugify(join('_', @list));
185
186 # Get response based on query parameter
187 my $response = $c->load_response($slug);
188 return $c->render(%$response);
189};
190
Akron0e1ed242018-10-11 13:22:00 +0200191############
192# Auth API #
193############
194
195# Request API token
Akron63d963b2019-07-05 15:35:51 +0200196get '/v1.0/auth/logout' => sub {
Akron0e1ed242018-10-11 13:22:00 +0200197 my $c = shift;
198
199 if (my $auth = $c->req->headers->header('Authorization')) {
200 if (my $jwt = $c->jwt_decode($auth)) {
201 my $user = $jwt->{username} if $jwt->{username};
202
203 $c->app->log->debug('Server-Logout: ' . $user);
204 return $c->render(json => { msg => [[0, 'Fine!']]});
205 };
206 };
207
208 return $c->render(status => 400, json => { error => [[0, 'No!']]});
209};
210
211
212# Request API token
Akron63d963b2019-07-05 15:35:51 +0200213get '/v1.0/auth/apiToken' => sub {
Akron0e1ed242018-10-11 13:22:00 +0200214 my $c = shift;
215
216 # Get auth header
217 my $auth = $c->req->headers->authorization;
218
219 # Authorization missing or not basic
220 if (!$auth || $auth !~ s/\s*Basic\s+//gi) {
221 return $c->render(
222 json => {
223 error => [[2, 'x']]
224 }
225 );
226 };
227
228 # Decode header
229 my ($username, $pwd) = @{b($auth)->b64_decode->split(':')->to_array};
230
231 # the password is 'pass'
232 if ($pwd) {
233
234 # the password is 'pass'
235 if ($pwd eq 'pass') {
236
237 # Render info with token
238 my $jwt = $c->jwt_encode(username => $username);
239
240 # Render in the Kustvakt fashion:
241 return $c->render(
242 format => 'html',
243 text => encode_json({
244 %{$jwt->claims},
245 expires => $jwt->expires,
246 token => $jwt->encode,
247 token_type => 'api_token'
248 })
249 );
Akron3d673062019-01-29 15:54:16 +0100250 }
251
252 elsif ($pwd eq 'ldaperr') {
253 return $c->render(
254 format => 'html',
255 status => 401,
256 json => {
257 "errors" => [[2022,"LDAP Authentication failed due to unknown user or password!"]]
258 }
259 );
Akron0e1ed242018-10-11 13:22:00 +0200260 };
261
262 return $c->render(
263 json => {
264 error => [[2004, undef]]
265 }
266 );
267 };
268
269 return $c->render(
270 json => {
271 error => [[2004, undef]]
272 }
273 );
274};
275
Akron33f5c672019-06-24 19:40:47 +0200276
277# Request API token
Akron63d963b2019-07-05 15:35:51 +0200278post '/v1.0/oauth2/token' => sub {
Akron33f5c672019-06-24 19:40:47 +0200279 my $c = shift;
280
Akron63d963b2019-07-05 15:35:51 +0200281 my $grant_type = $c->param('grant_type') // 'undefined';
282
283 if ($grant_type eq 'password') {
Akron33f5c672019-06-24 19:40:47 +0200284
Akron8bbbecf2019-07-01 18:57:30 +0200285 # Check for wrong client id
286 if ($c->param('client_id') ne '2') {
287 return $c->render(
288 json => {
289 "error_description" => "Unknown client with " . $_->{client_id},
290 "error" => "invalid_client"
291 },
292 status => 401
293 );
294 }
Akron33f5c672019-06-24 19:40:47 +0200295
Akron8bbbecf2019-07-01 18:57:30 +0200296 # Check for wrong client secret
297 elsif ($c->param('client_secret') ne 'k414m4r-s3cr3t') {
298 return $c->render(
299 json => {
300 "error_description" => "Invalid client credentials",
301 "error" => "invalid_client"
302 },
303 status => 401
304 );
305 }
Akron33f5c672019-06-24 19:40:47 +0200306
Akron8bbbecf2019-07-01 18:57:30 +0200307 # Check for wrong user name
308 elsif ($c->param('username') ne 'test') {
309 return $c->render(json => {
310 error => [[2004, undef]]
311 });
312 }
313
314 # Check for ldap error
315 elsif ($c->param('password') eq 'ldaperr') {
316 return $c->render(
317 format => 'html',
318 status => 401,
319 json => {
320 "errors" => [
321 [
322 2022,
323 "LDAP Authentication failed due to unknown user or password!"
324 ]
325 ]
326 }
327 );
328 }
329
330 # Check for wrong password
331 elsif ($c->param('password') ne 'pass') {
332 return $c->render(json => {
333 format => 'html',
334 status => 401,
Akron33f5c672019-06-24 19:40:47 +0200335 "errors" => [[2022,"LDAP Authentication failed due to unknown user or password!"]]
Akron8bbbecf2019-07-01 18:57:30 +0200336 });
337 }
338
339 # Return fine access
340 return $c->render(
341 json => {
342 "access_token" => "4dcf8784ccfd26fac9bdb82778fe60e2",
343 "refresh_token" => "hlWci75xb8atDiq3924NUSvOdtAh7Nlf9z",
344 "scope" => "all",
345 "token_type" => "Bearer",
346 "expires_in" => 86400
347 });
348 }
349
350 # Refresh token
Akron63d963b2019-07-05 15:35:51 +0200351 elsif ($grant_type eq 'refresh_token') {
Akron8bbbecf2019-07-01 18:57:30 +0200352 return $c->render(
353 status => 200,
354 json => {
355 "access_token" => "abcde",
356 "refresh_token" => "fghijk",
357 "token_type" => "Bearer",
358 "expires_in" => 86400
Akron33f5c672019-06-24 19:40:47 +0200359 }
360 );
361 }
362
Akron8bbbecf2019-07-01 18:57:30 +0200363 # Unknown token grant
364 else {
365 return $c->render(
Akron63d963b2019-07-05 15:35:51 +0200366 status => 400,
Akron8bbbecf2019-07-01 18:57:30 +0200367 json => {
368 "errors" => [
369 [
Akron63d963b2019-07-05 15:35:51 +0200370 0, "Grant Type unknown", $grant_type
Akron8bbbecf2019-07-01 18:57:30 +0200371 ]
372 ]
373 }
374 )
Akron33f5c672019-06-24 19:40:47 +0200375 }
Akron33f5c672019-06-24 19:40:47 +0200376};
377
378
379
Akron0e1ed242018-10-11 13:22:00 +0200380app->start;
381
382
383__END__
384
385
386 # Temporary:
387 my $collection_query = {
388 '@type' => "koral:docGroup",
389 "operation" => "operation:or",
390 "operands" => [
391 {
392 '@type' => "koral:docGroup",
393 "operation" => "operation:and",
394 "operands" => [
395 {
396 '@type' => "koral:doc",
397 "key" => "title",
398 "match" => "match:eq",
399 "value" => "Der Birnbaum",
400 "type" => "type:string"
401 },
402 {
403 '@type' => "koral:doc",
404 "key" => "pubPlace",
405 "match" => "match:eq",
406 "value" => "Mannheim",
407 "type" => "type:string"
408 },
409 {
410 '@type' => "koral:docGroup",
411 "operation" => "operation:or",
412 "operands" => [
413 {
414 '@type' => "koral:doc",
415 "key" => "subTitle",
416 "match" => "match:eq",
417 "value" => "Aufzucht oder Pflege",
418 "type" => "type:string"
419 },
420 {
421 '@type' => "koral:doc",
422 "key" => "subTitle",
423 "match" => "match:eq",
424 "value" => "Gedichte",
425 "type" => "type:string"
426 }
427 ]
428 }
429 ]
430 },
431 {
432 '@type' => "koral:doc",
433 "key" => "pubDate",
434 "match" => "match:geq",
435 "value" => "2015-03-05",
436 "type" => "type:date"
437 }
438 ]
439 };