Line 0
Link Here
|
|
|
1 |
package Koha::REST::V1::Plugins::KohaliciousSwagtenticator; |
2 |
|
3 |
use Modern::Perl; |
4 |
|
5 |
use base qw(Mojolicious::Plugin::Swagger2); |
6 |
|
7 |
use Digest::SHA qw(hmac_sha256_hex); |
8 |
use Try::Tiny; |
9 |
use Scalar::Util qw(blessed); |
10 |
use Data::Walk; |
11 |
|
12 |
use Koha::Auth; |
13 |
|
14 |
use Koha::Exception::BadAuthenticationToken; |
15 |
use Koha::Exception::UnknownProgramState; |
16 |
use Koha::Exception::NoPermission; |
17 |
|
18 |
use constant DEBUG => $ENV{SWAGGER2_DEBUG} || 0; |
19 |
|
20 |
|
21 |
|
22 |
################################################################################ |
23 |
###################### STARTING OVERLOADING SUBROUTINES ###################### |
24 |
################################################################################ |
25 |
|
26 |
|
27 |
|
28 |
=head _generate_request_handler |
29 |
@OVERLOADS Mojolicious::Plugin::Swagger2::_generate_request_handler() |
30 |
This is just a copy-paste of the parent function with a small incision to inject the Koha-authentication mechanism. |
31 |
Keep code changes minimal for upstream compatibility, so when problems arise, copy-pasting fixes them! |
32 |
|
33 |
=cut |
34 |
|
35 |
sub _generate_request_handler { |
36 |
my ($self, $method, $config) = @_; |
37 |
my $controller = $config->{'x-mojo-controller'} || $self->{controller}; # back compat |
38 |
|
39 |
return sub { |
40 |
my $c = shift; |
41 |
my $method_ref; |
42 |
|
43 |
unless (eval "require $controller;1") { |
44 |
$c->app->log->error($@); |
45 |
return $c->render_swagger($self->_not_implemented('Controller not implemented.'), {}, 501); |
46 |
} |
47 |
unless ($method_ref = $controller->can($method)) { |
48 |
$method_ref = $controller->can(sprintf '%s_%s', $method, lc $c->req->method) |
49 |
and warn "HTTP method name is not used in method name lookup anymore!"; |
50 |
} |
51 |
unless ($method_ref) { |
52 |
$c->app->log->error( |
53 |
qq(Can't locate object method "$method" via package "$controller. (Something is wrong in @{[$self->url]})")); |
54 |
return $c->render_swagger($self->_not_implemented('Method not implemented.'), {}, 501); |
55 |
} |
56 |
######################################### |
57 |
####### Koha-overload starts here ####### |
58 |
## Check for user api-key authentication and permissions. |
59 |
my ($error, $data, $statusCode) = _koha_authenticate($c, $config); |
60 |
return $c->render_swagger($error, $data, $statusCode) if $error; |
61 |
### END OF Koha-overload ### |
62 |
######################################### |
63 |
|
64 |
bless $c, $controller; # ugly hack? |
65 |
|
66 |
$c->delay( |
67 |
sub { |
68 |
my ($delay) = @_; |
69 |
my ($v, $input) = $self->_validate_input($c, $config); |
70 |
|
71 |
return $c->render_swagger($v, {}, 400) unless $v->{valid}; |
72 |
return $c->$method_ref($input, $delay->begin); |
73 |
}, |
74 |
sub { |
75 |
my $delay = shift; |
76 |
my $data = shift; |
77 |
my $status = shift || 200; |
78 |
my $format = $config->{responses}{$status} || $config->{responses}{default} || {}; |
79 |
my @err = $self->_validator->validate($data, $format->{schema}); |
80 |
|
81 |
return $c->render_swagger({errors => \@err, valid => Mojo::JSON->false}, $data, 500) if @err; |
82 |
return $c->render_swagger({}, $data, $status); |
83 |
}, |
84 |
); |
85 |
}; |
86 |
} |
87 |
|
88 |
|
89 |
|
90 |
################################################################################ |
91 |
######### END OF OVERLOADED SUBROUTINES, STARTING EXTENDED FEATURES ########## |
92 |
################################################################################ |
93 |
|
94 |
|
95 |
|
96 |
=head _koha_authenticate |
97 |
|
98 |
_koha_authenticate($c, $config); |
99 |
|
100 |
Checks all authentications in Koha, and prepares the data for a |
101 |
Mojolicious::Plugin::Swagger2->render_swagger($errors, $data, $statusCode) -response |
102 |
if authentication failed for some reason. |
103 |
|
104 |
@PARAM1 Mojolicious::Controller or a subclass |
105 |
@PARAM2 Reference to HASH, the "Operation Object" from Swagger2.0 specification, |
106 |
matching the given "Path Item Object"'s HTTP Verb. |
107 |
@RETURNS List of: HASH Ref, errors encountered |
108 |
HASH Ref, data to be sent |
109 |
String, status code from the Koha::REST::V1::check_key_auth() |
110 |
=cut |
111 |
|
112 |
sub _koha_authenticate { |
113 |
my ($c, $opObj) = @_; |
114 |
my ($error, $data, $statusCode); #define return values |
115 |
|
116 |
try { |
117 |
|
118 |
my $authParams = {}; |
119 |
$authParams->{authnotrequired} = 1 unless $opObj->{"x-koha-permission"}; |
120 |
Koha::Auth::authenticate($c, $opObj->{"x-koha-permission"}, $authParams); |
121 |
|
122 |
} catch { |
123 |
my $e = $_; |
124 |
if (blessed($e)) { |
125 |
my $swagger2DocumentationUrl = findConfigurationParameterFromAnyConfigurationFile($c->app->config(), 'swagger2DocumentationUrl') || ''; |
126 |
|
127 |
if ($e->isa('Koha::Exception::NoPermission') || |
128 |
$e->isa('Koha::Exception::LoginFailed') || |
129 |
$e->isa('Koha::Exception::UnknownObject') |
130 |
) { |
131 |
$error = {valid => Mojo::JSON->false, errors => [{message => $e->error, path => $c->req->url->path_query}, |
132 |
{message => "See '$swagger2DocumentationUrl' for how to properly authenticate to Koha"},]}; |
133 |
$data = {header => {"WWW-Authenticate" => "Koha $swagger2DocumentationUrl"}}; |
134 |
$statusCode = 401; #Throw Unauthorized with instructions on how to properly authorize. |
135 |
} |
136 |
elsif ($e->isa('Koha::Exception::BadParameter')) { |
137 |
$error = {valid => Mojo::JSON->false, errors => [{message => $e->error, path => $c->req->url->path_query}]}; |
138 |
$data = {}; |
139 |
$statusCode = 400; #Throw a Bad Request |
140 |
} |
141 |
elsif ($e->isa('Koha::Exception::VersionMismatch') || |
142 |
$e->isa('Koha::Exception::BadSystemPreference') || |
143 |
$e->isa('Koha::Exception::ServiceTemporarilyUnavailable') |
144 |
){ |
145 |
$error = {valid => Mojo::JSON->false, errors => [{message => $e->error, path => $c->req->url->path_query}]}; |
146 |
$data = {}; |
147 |
$statusCode = 503; #Throw Service Unavailable, but will be available later. |
148 |
} |
149 |
else { |
150 |
die $e; |
151 |
} |
152 |
} |
153 |
else { |
154 |
die $e; |
155 |
} |
156 |
}; |
157 |
return ($error, $data, $statusCode); |
158 |
} |
159 |
|
160 |
=head findConfigurationParameterFromAnyConfigurationFile |
161 |
|
162 |
Because we can use this REST API with CGI, or Plack, or Hypnotoad, or Morbo, ... |
163 |
We cannot know which configuration file we are currently using. |
164 |
$conf = {hypnotoad => {#conf params}, |
165 |
plack => {#conf params}, |
166 |
... |
167 |
} |
168 |
So find the needed markers from any configuration file. |
169 |
=cut |
170 |
|
171 |
sub findConfigurationParameterFromAnyConfigurationFile { |
172 |
my ($conf, $paramLookingFor) = @_; |
173 |
|
174 |
my $found; |
175 |
my $wanted = sub { |
176 |
if ($_ eq $paramLookingFor) { |
177 |
$found = $Data::Walk::container->{$_}; |
178 |
return (); |
179 |
} |
180 |
}; |
181 |
Data::Walk::walk( $wanted, $conf); |
182 |
return $found; |
183 |
} |
184 |
|
185 |
return 1; |