From 789338fd832a262ac834811a8850c37159152aa2 Mon Sep 17 00:00:00 2001 From: Agustin Moyano Date: Thu, 19 Dec 2019 15:50:49 -0300 Subject: [PATCH] Bug 24228: Add parameters to Koha::Object->to_api to automatically embed objects This patch adds the posibility to define 'x-koha-embed' parameter in swagger and in request headers. If 'x-koha-embed' header is present, in Koha::REST::V1::Auth it's compared with the whitelist defined in swagger through the 'x-koha-embed' parameter, placed at the same level of x-koha-authorization. Whitelist should be composed of DBIC relations (optinally chained by '.') that has the corresponding method in Koha::Object. To test: 1. Apply this patch 2. prove t/db_dependent/Koha/Object.t --- Koha/Exceptions/Authorization.pm | 5 ++++ Koha/Object.pm | 35 ++++++++++++++++++++++++++-- Koha/Objects.pm | 4 ++-- Koha/REST/Plugin/Objects.pm | 7 +++--- Koha/REST/V1/Auth.pm | 21 +++++++++++++++++ t/db_dependent/Koha/Object.t | 40 +++++++++++++++++++++++++++++++- 6 files changed, 104 insertions(+), 8 deletions(-) diff --git a/Koha/Exceptions/Authorization.pm b/Koha/Exceptions/Authorization.pm index a3c625aca8..1474ffac20 100644 --- a/Koha/Exceptions/Authorization.pm +++ b/Koha/Exceptions/Authorization.pm @@ -12,6 +12,11 @@ use Exception::Class ( description => 'Unauthorized', fields => ['required_permissions'] }, + 'Koha::Exceptions::Authorization::Embedded' => { + isa => 'Koha::Exceptions::Authorization', + description => 'Embedded documents unauthorized', + fields => ['embedded_item'] + }, ); diff --git a/Koha/Object.pm b/Koha/Object.pm index ce4c231591..553e53d28b 100644 --- a/Koha/Object.pm +++ b/Koha/Object.pm @@ -380,12 +380,12 @@ Returns a representation of the object, suitable for API output. =cut sub to_api { - my ( $self ) = @_; + my ( $self, $embeds ) = @_; my $json_object = $self->TO_JSON; # Rename attributes if there's a mapping if ( $self->can('to_api_mapping') ) { - foreach my $column ( keys %{$self->to_api_mapping} ) { + foreach my $column ( keys %{ $self->to_api_mapping } ) { my $mapped_column = $self->to_api_mapping->{$column}; if ( exists $json_object->{$column} && defined $mapped_column ) @@ -402,9 +402,40 @@ sub to_api { } } + if ($embeds) { + foreach my $embed (@$embeds) { + my ( $curr, $next ) = split /\s*\.\s*/, $embed, 2; + my @nxembeds; + + @nxembeds = ($next) if $next; + + my $children = $self->$curr; + if ( ref $children eq 'ARRAY' ) { + my @list; + my $pos = 0; + foreach my $child (@$children) { + my $res = $child->to_api( \@nxembeds ); + $res = { $json_object->{$curr}->[$pos], $res } + if defined $json_object->{$curr} + && defined $json_object->{$curr}->[$pos]; + push @list, $res; + $pos++; + } + $json_object->{$curr} = \@list; + } + else { + my $res = $children->to_api( \@nxembeds ); + $res = { $json_object->{$curr}, $res } + if defined $json_object->{$curr}; + $json_object->{$curr} = $res; + } + } + } + return $json_object; } + =head3 $object->unblessed_all_relateds my $everything_into_one_hashref = $object->unblessed_all_relateds diff --git a/Koha/Objects.pm b/Koha/Objects.pm index 31387e1442..05b1de467d 100644 --- a/Koha/Objects.pm +++ b/Koha/Objects.pm @@ -315,9 +315,9 @@ Returns a representation of the objects, suitable for API output . =cut sub to_api { - my ($self) = @_; + my ($self, $embeds) = @_; - return [ map { $_->to_api } $self->as_list ]; + return [ map { $_->to_api($embeds) } $self->as_list ]; } =head3 Koha::Objects->_wrap diff --git a/Koha/REST/Plugin/Objects.pm b/Koha/REST/Plugin/Objects.pm index 3d314a784e..96a1a94819 100644 --- a/Koha/REST/Plugin/Objects.pm +++ b/Koha/REST/Plugin/Objects.pm @@ -52,7 +52,6 @@ sub register { my $args = $c->validation->output; my $attributes = {}; - # Extract reserved params my ( $filtered_params, $reserved_params ) = $c->extract_reserved_params($args); @@ -82,6 +81,8 @@ sub register { $filtered_params = $c->build_query_params( $filtered_params, $reserved_params ); } + my $embeds = $c->stash('koha.embed'); + # Perform search my $objects = $objects_set->search( $filtered_params, $attributes ); @@ -94,8 +95,8 @@ sub register { my @objects_list = map { ( defined $to_api ) - ? $to_api->( $_->TO_JSON ) - : $_->TO_JSON + ? $to_api->( $_->TO_JSON, $embeds ) + : $_->to_api($embeds); } $objects->as_list; return \@objects_list; diff --git a/Koha/REST/V1/Auth.pm b/Koha/REST/V1/Auth.pm index 8bffc977da..7c9d9bb929 100644 --- a/Koha/REST/V1/Auth.pm +++ b/Koha/REST/V1/Auth.pm @@ -137,6 +137,27 @@ sub authenticate_api_request { my $user; my $spec = $c->match->endpoint->pattern->defaults->{'openapi.op_spec'}; + + my $embed_header = $c->req->headers->header('x-koha-embed'); + + if($embed_header) { + my $embed = $spec->{'x-koha-embed'}; + + Koha::Exceptions::Authorization::Embedded->throw( + error => 'No embedded items are authorised for this endpoint. Remove your x-koha-embed headers.' + ) unless $embed; + + my @to_embed; + foreach my $eh (split /\s*,\s*/, $embed_header) { + my $matches = grep {lc $_ eq lc $eh} @$embed; + Koha::Exceptions::Authorization::Embedded->throw( + error => 'Embeding '.$eh. ' is not authorised. Check your x-koha-embed headers or remove it.' + ) unless $matches; + push @to_embed, $eh; + } + $c->stash('koha.embed' => \@to_embed); + } + my $authorization = $spec->{'x-koha-authorization'}; my $authorization_header = $c->req->headers->authorization; diff --git a/t/db_dependent/Koha/Object.t b/t/db_dependent/Koha/Object.t index a37695bc1f..af97834a7d 100755 --- a/t/db_dependent/Koha/Object.t +++ b/t/db_dependent/Koha/Object.t @@ -215,7 +215,7 @@ subtest 'TO_JSON tests' => sub { subtest "to_api() tests" => sub { - plan tests => 11; + plan tests => 18; $schema->storage->txn_begin; @@ -262,6 +262,44 @@ subtest "to_api() tests" => sub { my $illrequest = $builder->build_object({ class => 'Koha::Illrequests' }); is_deeply( $illrequest->to_api, $illrequest->TO_JSON, 'If no to_api_method present, return TO_JSON' ); + my $item_class = Test::MockModule->new('Koha::Item'); + $item_class->mock( 'to_api_mapping', + sub { + return { + itemnumber => 'item_id' + }; + } + ); + + my $hold_class = Test::MockModule->new('Koha::Hold'); + $hold_class->mock( 'to_api_mapping', + sub { + return { + reserve_id => 'hold_id' + }; + } + ); + + my $biblio = $builder->build_sample_biblio(); + my $item = $builder->build_sample_item({ biblionumber => $biblio->biblionumber }); + my $hold = $builder->build_object({ class => 'Koha::Holds', value => { itemnumber => $item->itemnumber } }); + + my @embeds = ('items'); + + my $biblio_api = $biblio->to_api(\@embeds); + + ok(exists $biblio_api->{items}, 'Items where embedded in biblio results'); + is($biblio_api->{items}->[0]->{item_id}, $item->itemnumber, 'Item matches'); + ok(!exists $biblio_api->{items}->[0]->{holds}, 'No holds info should be embedded yet'); + + @embeds = ('items.holds'); + $biblio_api = $biblio->to_api(\@embeds); + + ok(exists $biblio_api->{items}, 'Items where embedded in biblio results'); + is($biblio_api->{items}->[0]->{item_id}, $item->itemnumber, 'Item still matches'); + ok(exists $biblio_api->{items}->[0]->{holds}, 'Holds info should be embedded'); + is($biblio_api->{items}->[0]->{holds}->[0]->{hold_id}, $hold->reserve_id, 'Hold matches'); + $schema->storage->txn_rollback; }; -- 2.17.1