#
# This file is part of Koha.
#
-# Koha is free software; you can redistribute it and/or modify it under the
-# terms of the GNU General Public License as published by the Free Software
-# Foundation; either version 3 of the License, or (at your option) any later
-# version.
+# Koha is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
#
-# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
-# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+# Koha is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
#
-# You should have received a copy of the GNU General Public License along
-# with Koha; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+# You should have received a copy of the GNU General Public License
+# along with Koha; if not, see <http://www.gnu.org/licenses>.
use Modern::Perl;
use Mojo::Base 'Mojolicious::Controller';
-use C4::Auth qw( check_cookie_auth get_session haspermission );
+use C4::Auth qw( check_cookie_auth checkpw_internal get_session haspermission );
use C4::Context;
use Koha::ApiKeys;
use Koha::Account::Lines;
use Koha::Checkouts;
use Koha::Holds;
+use Koha::Libraries;
use Koha::OAuth;
use Koha::OAuthAccessTokens;
use Koha::Old::Checkouts;
use Koha::Exceptions::Authentication;
use Koha::Exceptions::Authorization;
+use MIME::Base64 qw( decode_base64 );
use Module::Load::Conditional;
use Scalar::Util qw( blessed );
-use Try::Tiny;
+use Try::Tiny qw( catch try );
=head1 NAME
=cut
sub under {
- my $c = shift->openapi->valid_input or return;;
+ my ( $c ) = @_;
my $status = 0;
+
try {
- $status = authenticate_api_request($c);
+ # /api/v1/{namespace}
+ my $namespace = $c->req->url->to_abs->path->[2] // '';
+
+ my $is_public = 0; # By default routes are not public
+ my $is_plugin = 0;
+
+ if ( $namespace eq 'public' ) {
+ $is_public = 1;
+ } elsif ( $namespace eq 'contrib' ) {
+ $is_plugin = 1;
+ }
+
+ if ( $is_public
+ and !C4::Context->preference('RESTPublicAPI') )
+ {
+ Koha::Exceptions::Authorization->throw(
+ "Configuration prevents the usage of this endpoint by unprivileged users");
+ }
+
+ if ( $c->req->url->to_abs->path eq '/api/v1/oauth/token' ) {
+ # Requesting a token shouldn't go through the API authenticaction chain
+ $status = 1;
+ }
+ elsif ( $namespace eq '' or $namespace eq '.html' ) {
+ $status = 1;
+ }
+ else {
+ $status = authenticate_api_request($c, { is_public => $is_public, is_plugin => $is_plugin });
+ }
} catch {
unless (blessed($_)) {
return $c->render(status => 401, json => { error => $_->error });
}
elsif ($_->isa('Koha::Exceptions::Authentication')) {
- return $c->render(status => 500, json => { error => $_->error });
+ return $c->render(status => 401, json => { error => $_->error });
}
elsif ($_->isa('Koha::Exceptions::BadParameter')) {
return $c->render(status => 400, json => $_->error );
required_permissions => $_->required_permissions,
});
}
+ elsif ($_->isa('Koha::Exceptions::Authorization')) {
+ return $c->render(status => 403, json => { error => $_->error });
+ }
elsif ($_->isa('Koha::Exceptions')) {
return $c->render(status => 500, json => { error => $_->error });
}
=cut
sub authenticate_api_request {
- my ( $c ) = @_;
+ my ( $c, $params ) = @_;
my $user;
- my $spec = $c->match->endpoint->pattern->defaults->{'openapi.op_spec'};
+ # The following supports retrieval of spec with Mojolicious::Plugin::OpenAPI@1.17 and later (first one)
+ # and older versions (second one).
+ # TODO: remove the latter 'openapi.op_spec' if minimum version is bumped to at least 1.17.
+ my $spec = $c->openapi->spec || $c->match->endpoint->pattern->defaults->{'openapi.op_spec'};
+
+ $c->stash_embed({ spec => $spec });
+ $c->stash_overrides();
+
+ my $cookie_auth = 0;
+
my $authorization = $spec->{'x-koha-authorization'};
my $authorization_header = $c->req->headers->authorization;
);
}
}
+ elsif ( $authorization_header and $authorization_header =~ /^Basic / ) {
+ unless ( C4::Context->preference('RESTBasicAuth') ) {
+ Koha::Exceptions::Authentication::Required->throw(
+ error => 'Basic authentication disabled'
+ );
+ }
+ $user = $c->_basic_auth( $authorization_header );
+ unless ( $user ) {
+ # If we have "Authorization: Basic" header and authentication
+ # failed, do not try other authentication means
+ Koha::Exceptions::Authentication::Required->throw(
+ error => 'Authentication failure.'
+ );
+ }
+ }
else {
my $cookie = $c->cookie('CGISESSID');
{ remote_addr => $remote_addr });
if ($status eq "ok") {
my $session = get_session($sessionID);
- $user = Koha::Patrons->find($session->param('number'));
- # $c->stash('koha.user' => $user);
+ $user = Koha::Patrons->find( $session->param('number') )
+ unless $session->param('sessiontype')
+ and $session->param('sessiontype') eq 'anon';
+ $cookie_auth = 1;
}
elsif ($status eq "maintenance") {
Koha::Exceptions::UnderMaintenance->throw(
}
$c->stash('koha.user' => $user);
+ C4::Context->interface('api');
+
+ if ( $user and !$cookie_auth ) { # cookie-auth sets this and more, don't mess with that
+ $c->_set_userenv( $user );
+ }
- # We do not need any authorization
- unless ($authorization) {
+ if ( !$authorization and
+ ( $params->{is_public} and
+ ( C4::Context->preference('RESTPublicAnonymousRequests') or
+ $user) or $params->{is_plugin} ) ) {
+ # We do not need any authorization
# Check the parameters
validate_query_parameters( $c, $spec );
return 1;
}
+ else {
+ # We are required authorizarion, there needs
+ # to be an identified user
+ Koha::Exceptions::Authentication::Required->throw(
+ error => 'Authentication failure.' )
+ unless $user;
+ }
+
my $permissions = $authorization->{'permissions'};
# Check if the user is authorized
- if ( haspermission($user->userid, $permissions)
+ if ( ( defined($permissions) and haspermission($user->userid, $permissions) )
or allow_owner($c, $authorization, $user)
or allow_guarantor($c, $authorization, $user) ) {
);
}
+=head3 validate_query_parameters
+
+Validates the query parameters against the spec.
+
+=cut
+
sub validate_query_parameters {
my ( $c, $action_spec ) = @_;
) if @errors;
}
-
=head3 allow_owner
Allows access to object for its owner.
return;
}
- my $guarantees = $user->guarantees->as_list;
+ my $guarantees = $user->guarantee_relationships->guarantees->as_list;
foreach my $guarantee (@{$guarantees}) {
return 1 if check_object_ownership($c, $guarantee);
}
return $reserve && $user->borrowernumber == $reserve->borrowernumber;
}
+=head3 _basic_auth
+
+Internal method that performs Basic authentication.
+
+=cut
+
+sub _basic_auth {
+ my ( $c, $authorization_header ) = @_;
+
+ my ( $type, $credentials ) = split / /, $authorization_header;
+
+ unless ($credentials) {
+ Koha::Exceptions::Authentication::Required->throw( error => 'Authentication failure.' );
+ }
+
+ my $decoded_credentials = decode_base64( $credentials );
+ my ( $user_id, $password ) = split( /:/, $decoded_credentials, 2 );
+
+ my $dbh = C4::Context->dbh;
+ unless ( checkpw_internal($dbh, $user_id, $password ) ) {
+ Koha::Exceptions::Authorization::Unauthorized->throw( error => 'Invalid password' );
+ }
+
+ return Koha::Patrons->find({ userid => $user_id });
+}
+
+=head3 _set_userenv
+
+ $c->_set_userenv( $patron );
+
+Internal method that sets C4::Context->userenv
+
+=cut
+
+sub _set_userenv {
+ my ( $c, $patron ) = @_;
+
+ my $passed_library_id = $c->req->headers->header('x-koha-library');
+ my $THE_library;
+
+ if ( $passed_library_id ) {
+ $THE_library = Koha::Libraries->find( $passed_library_id );
+ Koha::Exceptions::Authorization::Unauthorized->throw(
+ "Unauthorized attempt to set library to $passed_library_id"
+ ) unless $THE_library and $patron->can_log_into($THE_library);
+ }
+ else {
+ $THE_library = $patron->library;
+ }
+
+ C4::Context->_new_userenv( $patron->borrowernumber );
+ C4::Context->set_userenv(
+ $patron->borrowernumber, # number,
+ $patron->userid, # userid,
+ $patron->cardnumber, # cardnumber
+ $patron->firstname, # firstname
+ $patron->surname, # surname
+ $THE_library->branchcode, # branch
+ $THE_library->branchname, # branchname
+ $patron->flags, # flags,
+ $patron->email, # emailaddress
+ );
+
+ return $c;
+}
+
1;