View | Details | Raw Unified | Return to bug 13920
Collapse All | Expand All

(-)a/Koha/REST/V1.pm (-6 / +103 lines)
Lines 3-14 package Koha::REST::V1; Link Here
3
use Modern::Perl;
3
use Modern::Perl;
4
use Mojo::Base 'Mojolicious';
4
use Mojo::Base 'Mojolicious';
5
use Mojo::Log;
5
use Mojo::Log;
6
use Mojolicious::Plugins; #Extend the plugin system
6
use Data::Walk;
7
use Scalar::Util qw(blessed);
8
use Try::Tiny;
7
9
8
use Koha::Borrower;
10
use Koha::Borrower;
9
use Koha::Borrowers;
11
use Koha::Borrowers;
10
use Koha::ApiKey;
12
use Koha::ApiKey;
11
use Koha::ApiKeys;
13
use Koha::ApiKeys;
14
use Koha::Auth;
12
15
13
16
14
=head startup
17
=head startup
Lines 65-76 sub startup { Link Here
65
    $self->setKohaParamLogging();
68
    $self->setKohaParamLogging();
66
    $self->setKohaParamConfig();
69
    $self->setKohaParamConfig();
67
70
71
    # Force charset=utf8 in Content-Type header for JSON responses
72
    $self->types->type(json => 'application/json; charset=utf8');
68
73
69
    ##Add the Koha namespace to the plugin engine to find plugins from.
74
    $self->plugin(Swagger2 => {
70
    my $plugin = $self->plugins();
71
    push @{$plugin->namespaces}, 'Koha::REST::V1::Plugins';
72
73
    $self->plugin(KohaliciousSwagtenticator => {
74
        url => $self->home->rel_file("api/v1/swagger.json"),
75
        url => $self->home->rel_file("api/v1/swagger.json"),
75
    });
76
    });
76
}
77
}
Lines 106-109 sub setKohaParamLogging { Link Here
106
        print __PACKAGE__."::startup():> No logfile given, defaulting to STDERR. Define your logfile and loglevel to the MOJO_LOGFILES and MOJO_LOGLEVEL environmental variables. If you want foreground logging, set the MOJO_LOGFILES as undef.\n";
107
        print __PACKAGE__."::startup():> No logfile given, defaulting to STDERR. Define your logfile and loglevel to the MOJO_LOGFILES and MOJO_LOGLEVEL environmental variables. If you want foreground logging, set the MOJO_LOGFILES as undef.\n";
107
    }
108
    }
108
}
109
}
110
111
=head _koha_authenticate
112
113
    _koha_authenticate($c, $config);
114
115
Checks all authentications in Koha, and prepares the data for a
116
Mojolicious::Plugin::Swagger2->render_swagger($errors, $data, $statusCode) -response
117
if authentication failed for some reason.
118
119
@PARAM1 Mojolicious::Controller or a subclass
120
@PARAM2 Reference to HASH, the "Operation Object" from Swagger2.0 specification,
121
                            matching the given "Path Item Object"'s HTTP Verb.
122
@RETURNS List of: HASH Ref, errors encountered
123
                  HASH Ref, data to be sent
124
                  String, status code from the Koha::REST::V1::check_key_auth()
125
=cut
126
127
sub koha_authenticate {
128
    my ($next, $c, $opObj) = @_;
129
    my ($error, $data, $statusCode); #define return values
130
131
    try {
132
133
        my $authParams = {};
134
        $authParams->{authnotrequired} = 1 unless $opObj->{"x-koha-permission"};
135
        my ($borrower, $cookie) = Koha::Auth::authenticate($c, $opObj->{"x-koha-permission"}, $authParams);
136
137
    } catch {
138
      my $e = $_;
139
      if (blessed($e)) {
140
        my $swagger2DocumentationUrl = _findConfigurationParameterFromAnyConfigurationFile($c->app->config(), 'swagger2DocumentationUrl') || '';
141
142
        if ($e->isa('Koha::Exception::NoPermission') ||
143
            $e->isa('Koha::Exception::LoginFailed') ||
144
            $e->isa('Koha::Exception::UnknownObject')
145
           ) {
146
          $error = [{message => $e->error, path => $c->req->url->path_query},
147
                    {message => "See '$swagger2DocumentationUrl' for how to properly authenticate to Koha"},];
148
          $data = {header => {"WWW-Authenticate" => "Koha $swagger2DocumentationUrl"}};
149
          $statusCode = 401; #Throw Unauthorized with instructions on how to properly authorize.
150
        }
151
        elsif ($e->isa('Koha::Exception::BadParameter')) {
152
          $error = [{message => $e->error, path => $c->req->url->path_query}];
153
          $data = {};
154
          $statusCode = 400; #Throw a Bad Request
155
        }
156
        elsif ($e->isa('Koha::Exception::VersionMismatch') ||
157
               $e->isa('Koha::Exception::BadSystemPreference') ||
158
               $e->isa('Koha::Exception::ServiceTemporarilyUnavailable')
159
              ){
160
          $error = [{message => $e->error, path => $c->req->url->path_query}];
161
          $data = {};
162
          $statusCode = 503; #Throw Service Unavailable, but will be available later.
163
        }
164
        else {
165
          $e->rethrow();
166
        }
167
      }
168
      else {
169
        die $e;
170
      }
171
    };
172
    return $next->($c) unless ($error || $data || $statusCode);
173
    return $c->render_swagger(
174
        {errors => $error},
175
        $data,
176
        $statusCode,
177
    );
178
}
179
180
=head findConfigurationParameterFromAnyConfigurationFile
181
182
Because we can use this REST API with CGI, or Plack, or Hypnotoad, or Morbo, ...
183
We cannot know which configuration file we are currently using.
184
$conf = {hypnotoad => {#conf params},
185
         plack     => {#conf params},
186
         ...
187
        }
188
So find the needed markers from any configuration file.
189
=cut
190
191
sub _findConfigurationParameterFromAnyConfigurationFile {
192
  my ($conf, $paramLookingFor) = @_;
193
194
  my $found;
195
  my $wanted = sub {
196
    if ($_ eq $paramLookingFor) {
197
      $found = $Data::Walk::container->{$_};
198
      return ();
199
    }
200
  };
201
  Data::Walk::walk( $wanted, $conf);
202
  return $found;
203
}
204
109
1;
205
1;
206
(-)a/Koha/REST/V1/Plugins/KohaliciousSwagtenticator.pm (-185 lines)
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;
(-)a/api/v1/swagger.json (-1 / +1 lines)
Lines 12-17 Link Here
12
      "url": "http://koha-community.org/"
12
      "url": "http://koha-community.org/"
13
    }
13
    }
14
  },
14
  },
15
  "x-mojo-around-action": "Koha::REST::V1::koha_authenticate",
15
  "basePath": "/api/v1",
16
  "basePath": "/api/v1",
16
  "paths": {
17
  "paths": {
17
    "/borrowers": {
18
    "/borrowers": {
18
- 

Return to bug 13920