|
Lines 1-185
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; |