blob: 359f78cf1f6915f961549b06cac20704d382086a [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
68get '/' => 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
74get '/search' => sub {
75 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
136get '/corpus/:corpusId/:docId/:textId' => sub {
137 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
155get '/corpus/:corpusId/:docId/:textId/:matchId/matchInfo' => sub {
156 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
175get '/statistics' => sub {
176 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
196get '/auth/logout' => sub {
197 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
213get '/auth/apiToken' => sub {
214 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
278post '/oauth2/token' => sub {
279 my $c = shift;
280
Akron8bbbecf2019-07-01 18:57:30 +0200281 if ($c->param('grant_type') eq 'password') {
Akron33f5c672019-06-24 19:40:47 +0200282
Akron8bbbecf2019-07-01 18:57:30 +0200283 # Check for wrong client id
284 if ($c->param('client_id') ne '2') {
285 return $c->render(
286 json => {
287 "error_description" => "Unknown client with " . $_->{client_id},
288 "error" => "invalid_client"
289 },
290 status => 401
291 );
292 }
Akron33f5c672019-06-24 19:40:47 +0200293
Akron8bbbecf2019-07-01 18:57:30 +0200294 # Check for wrong client secret
295 elsif ($c->param('client_secret') ne 'k414m4r-s3cr3t') {
296 return $c->render(
297 json => {
298 "error_description" => "Invalid client credentials",
299 "error" => "invalid_client"
300 },
301 status => 401
302 );
303 }
Akron33f5c672019-06-24 19:40:47 +0200304
Akron8bbbecf2019-07-01 18:57:30 +0200305 # Check for wrong user name
306 elsif ($c->param('username') ne 'test') {
307 return $c->render(json => {
308 error => [[2004, undef]]
309 });
310 }
311
312 # Check for ldap error
313 elsif ($c->param('password') eq 'ldaperr') {
314 return $c->render(
315 format => 'html',
316 status => 401,
317 json => {
318 "errors" => [
319 [
320 2022,
321 "LDAP Authentication failed due to unknown user or password!"
322 ]
323 ]
324 }
325 );
326 }
327
328 # Check for wrong password
329 elsif ($c->param('password') ne 'pass') {
330 return $c->render(json => {
331 format => 'html',
332 status => 401,
Akron33f5c672019-06-24 19:40:47 +0200333 "errors" => [[2022,"LDAP Authentication failed due to unknown user or password!"]]
Akron8bbbecf2019-07-01 18:57:30 +0200334 });
335 }
336
337 # Return fine access
338 return $c->render(
339 json => {
340 "access_token" => "4dcf8784ccfd26fac9bdb82778fe60e2",
341 "refresh_token" => "hlWci75xb8atDiq3924NUSvOdtAh7Nlf9z",
342 "scope" => "all",
343 "token_type" => "Bearer",
344 "expires_in" => 86400
345 });
346 }
347
348 # Refresh token
349 elsif ($c->param('grant_type') eq 'refresh_token') {
350 return $c->render(
351 status => 200,
352 json => {
353 "access_token" => "abcde",
354 "refresh_token" => "fghijk",
355 "token_type" => "Bearer",
356 "expires_in" => 86400
Akron33f5c672019-06-24 19:40:47 +0200357 }
358 );
359 }
360
Akron8bbbecf2019-07-01 18:57:30 +0200361 # Unknown token grant
362 else {
363 return $c->render(
364 json => {
365 "errors" => [
366 [
367 0, "Grant Type unknown", $c->param("grant_type")
368 ]
369 ]
370 }
371 )
Akron33f5c672019-06-24 19:40:47 +0200372 }
Akron33f5c672019-06-24 19:40:47 +0200373};
374
375
376
Akron0e1ed242018-10-11 13:22:00 +0200377app->start;
378
379
380__END__
381
382
383 # Temporary:
384 my $collection_query = {
385 '@type' => "koral:docGroup",
386 "operation" => "operation:or",
387 "operands" => [
388 {
389 '@type' => "koral:docGroup",
390 "operation" => "operation:and",
391 "operands" => [
392 {
393 '@type' => "koral:doc",
394 "key" => "title",
395 "match" => "match:eq",
396 "value" => "Der Birnbaum",
397 "type" => "type:string"
398 },
399 {
400 '@type' => "koral:doc",
401 "key" => "pubPlace",
402 "match" => "match:eq",
403 "value" => "Mannheim",
404 "type" => "type:string"
405 },
406 {
407 '@type' => "koral:docGroup",
408 "operation" => "operation:or",
409 "operands" => [
410 {
411 '@type' => "koral:doc",
412 "key" => "subTitle",
413 "match" => "match:eq",
414 "value" => "Aufzucht oder Pflege",
415 "type" => "type:string"
416 },
417 {
418 '@type' => "koral:doc",
419 "key" => "subTitle",
420 "match" => "match:eq",
421 "value" => "Gedichte",
422 "type" => "type:string"
423 }
424 ]
425 }
426 ]
427 },
428 {
429 '@type' => "koral:doc",
430 "key" => "pubDate",
431 "match" => "match:geq",
432 "value" => "2015-03-05",
433 "type" => "type:date"
434 }
435 ]
436 };