blob: e32365e7c3cd45438bf6a645ce043297856c2c5f [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
281 # Check for wrong client id
282 if ($c->param('client_id') ne '2') {
283 return $c->render(
284 json => {
285 "error_description" => "Unknown client with " . $_->{client_id},
286 "error" => "invalid_client"
287 },
288 status => 401
289 );
290 }
291
292 # Check for wrong client secret
293 elsif ($c->param('client_secret') ne 'k414m4r-s3cr3t') {
294 return $c->render(
295 json => {
296 "error_description" => "Invalid client credentials",
297 "error" => "invalid_client"
298 },
299 status => 401
300 );
301 }
302
303 # Check for wrong user name
304 elsif ($c->param('username') ne 'test') {
305 return $c->render(json => {
306 error => [[2004, undef]]
307 });
308 }
309
310 # Check for ldap error
311 elsif ($c->param('password') eq 'ldaperr') {
312 return $c->render(
313 format => 'html',
314 status => 401,
315 json => {
316 "errors" => [[2022,"LDAP Authentication failed due to unknown user or password!"]]
317 }
318 );
319 }
320
321 # Check for wrong password
322 elsif ($c->param('password') ne 'pass') {
323 return $c->render(json => {
324 error => [[2004, undef]]
325 });
326 }
327
328 # Return fine access
329 return $c->render(
330 json => {
331 "access_token" => "4dcf8784ccfd26fac9bdb82778fe60e2",
332 "refresh_token" => "hlWci75xb8atDiq3924NUSvOdtAh7Nlf9z",
333 "scope" => "all",
334 "token_type" => "Bearer",
335 "expires_in" => 86400
336 });
337};
338
339
340
Akron0e1ed242018-10-11 13:22:00 +0200341app->start;
342
343
344__END__
345
346
347 # Temporary:
348 my $collection_query = {
349 '@type' => "koral:docGroup",
350 "operation" => "operation:or",
351 "operands" => [
352 {
353 '@type' => "koral:docGroup",
354 "operation" => "operation:and",
355 "operands" => [
356 {
357 '@type' => "koral:doc",
358 "key" => "title",
359 "match" => "match:eq",
360 "value" => "Der Birnbaum",
361 "type" => "type:string"
362 },
363 {
364 '@type' => "koral:doc",
365 "key" => "pubPlace",
366 "match" => "match:eq",
367 "value" => "Mannheim",
368 "type" => "type:string"
369 },
370 {
371 '@type' => "koral:docGroup",
372 "operation" => "operation:or",
373 "operands" => [
374 {
375 '@type' => "koral:doc",
376 "key" => "subTitle",
377 "match" => "match:eq",
378 "value" => "Aufzucht oder Pflege",
379 "type" => "type:string"
380 },
381 {
382 '@type' => "koral:doc",
383 "key" => "subTitle",
384 "match" => "match:eq",
385 "value" => "Gedichte",
386 "type" => "type:string"
387 }
388 ]
389 }
390 ]
391 },
392 {
393 '@type' => "koral:doc",
394 "key" => "pubDate",
395 "match" => "match:geq",
396 "value" => "2015-03-05",
397 "type" => "type:date"
398 }
399 ]
400 };