X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FCatalyst%2FController%2FDBIC%2FAPI%2FRequestArguments.pm;h=523c07f9e4b4df0788da67b6efcdda5a75a108b4;hb=5fca1d92a1bbeb68d4c810e7e779cfb8877dff60;hp=ad9280e4e477d6d9f95ba41bab8d6aa7907dead5;hpb=617efaa8a88cddd04c6ae42c14c4a483c78a329d;p=catagits%2FCatalyst-Controller-DBIC-API.git diff --git a/lib/Catalyst/Controller/DBIC/API/RequestArguments.pm b/lib/Catalyst/Controller/DBIC/API/RequestArguments.pm index ad9280e..523c07f 100644 --- a/lib/Catalyst/Controller/DBIC/API/RequestArguments.pm +++ b/lib/Catalyst/Controller/DBIC/API/RequestArguments.pm @@ -6,56 +6,49 @@ use Catalyst::Controller::DBIC::API::Types(':all'); use MooseX::Types::Moose(':all'); use Scalar::Util('reftype'); use Data::Dumper; +use Catalyst::Controller::DBIC::API::Validator; use namespace::autoclean; use Catalyst::Controller::DBIC::API::JoinBuilder; - =attribute_private search_validator A Catalyst::Controller::DBIC::API::Validator instance used solely to validate search parameters =cut -with 'MooseX::Role::BuildInstanceOf' => -{ - 'target' => 'Catalyst::Controller::DBIC::API::Validator', - 'prefix' => 'search_validator', -}; - =attribute_private select_validator A Catalyst::Controller::DBIC::API::Validator instance used solely to validate select parameters =cut -with 'MooseX::Role::BuildInstanceOf' => -{ - 'target' => 'Catalyst::Controller::DBIC::API::Validator', - 'prefix' => 'select_validator', -}; - =attribute_private prefetch_validator A Catalyst::Controller::DBIC::API::Validator instance used solely to validate prefetch parameters =cut -with 'MooseX::Role::BuildInstanceOf' => -{ - 'target' => 'Catalyst::Controller::DBIC::API::Validator', - 'prefix' => 'prefetch_validator', -}; +has [qw( search_validator select_validator )] => ( + is => 'ro', + isa => 'Catalyst::Controller::DBIC::API::Validator', + lazy => 1, + builder => '_build_validator', +); + +sub _build_validator { + return Catalyst::Controller::DBIC::API::Validator->new; +} parameter static => ( isa => Bool, default => 0 ); role { - + my $p = shift; - + if($p->static) { - requires qw/check_has_relation check_column_relation/; + requires qw/check_has_relation check_column_relation prefetch_allows /; } else { @@ -146,86 +139,26 @@ prefetch is passed to ->search to optimize the number of database fetches for jo ( is => 'ro', writer => '_set_prefetch', - isa => Prefetch, + isa => Prefetch, default => sub { $p->static ? [] : undef }, coerce => 1, trigger => sub { my ($self, $new) = @_; - if($self->has_prefetch_allows and @{$self->prefetch_allows}) - { - foreach my $pf (@$new) - { - if(HashRef->check($pf)) - { - die qq|'${\Dumper($pf)}' is not an allowed prefetch in: ${\join("\n", @{$self->prefetch_validator->templates})}| - unless $self->prefetch_validator->validate($pf)->[0]; - } - else - { - die qq|'$pf' is not an allowed prefetch in: ${\join("\n", @{$self->prefetch_validator->templates})}| - unless $self->prefetch_validator->validate({$pf => 1})->[0]; - } - } - } - else - { - return if not defined($new); - die 'Prefetching is not allowed' if @$new; - } - }, - ); - -=attribute_public prefetch_allows is: ro, isa: ArrayRef[ArrayRef|Str|HashRef] - -prefetch_allows limits what relations may be prefetched when executing searches with joins. This is necessary to avoid denial of service attacks in form of queries which would return a large number of data and unwanted disclosure of data. -Like the synopsis in DBIC::API shows, you can declare a "template" of what is allowed (by using an '*'). Each element passed in, will be converted into a Data::DPath and added to the validator. - - prefetch_allows => [ 'cds', { cds => tracks }, { cds => producers } ] # to be explicit - prefetch_allows => [ 'cds', { cds => '*' } ] # wildcard means the same thing - -=cut - - has prefetch_allows => - ( - is => 'ro', - writer => '_set_prefetch_allows', - isa => ArrayRef[ArrayRef|Str|HashRef], - default => sub { [ ] }, - predicate => 'has_prefetch_allows', - trigger => sub - { - my ($self, $new) = @_; - - sub check_rel { - my ($self, $rel, $static) = @_; - if(ArrayRef->check($rel)) - { - foreach my $rel_sub (@$rel) - { - $self->check_rel($rel_sub, $static); - } - } - elsif(HashRef->check($rel)) + foreach my $pf (@$new) + { + if(HashRef->check($pf)) { - while(my($k,$v) = each %$rel) - { - $self->check_has_relation($k, $v, undef, $static); - } - $self->prefetch_validator->load($rel); + die qq|'${\Dumper($pf)}' is not an allowed prefetch in: ${\join("\n", @{$self->prefetch_validator->templates})}| + unless $self->prefetch_validator->validate($pf)->[0]; } else { - $self->check_has_relation($rel, undef, undef, $static); - $self->prefetch_validator->load($rel); + die qq|'$pf' is not an allowed prefetch in: ${\join("\n", @{$self->prefetch_validator->templates})}| + unless $self->prefetch_validator->validate({$pf => 1})->[0]; } } - - foreach my $rel (@$new) - { - $self->check_rel($rel, $p->static); - } }, ); @@ -269,7 +202,7 @@ Please see L for details on how the format work trigger => sub { my ($self, $new) = @_; - + if($self->has_search_exposes and @{$self->search_exposes}) { foreach my $foo (@$new) @@ -292,7 +225,7 @@ Please see L for details on how the format work } } } - + my ($search_parameters, $search_attributes) = $self->generate_parameters_attributes($new); $self->_set_search_parameters($search_parameters); $self->_set_search_attributes($search_attributes); @@ -384,7 +317,7 @@ Please see L for more details. default => sub { $p->static ? [] : undef }, coerce => 1, trigger => sub - { + { my ($self, $new) = @_; if($self->has_select_exposes) { @@ -462,10 +395,12 @@ request_data holds the raw (but deserialized) data for ths request is => 'ro', isa => HashRef, writer => '_set_request_data', + predicate => 'has_request_data', trigger => sub { my ($self, $new) = @_; my $controller = $self->_controller; + return unless defined($new) && keys %$new; $self->_set_prefetch($new->{$controller->prefetch_arg}) if exists $new->{$controller->prefetch_arg}; $self->_set_select($new->{$controller->select_arg}) if exists $new->{$controller->select_arg}; $self->_set_as($new->{$controller->as_arg}) if exists $new->{$controller->as_arg}; @@ -489,7 +424,7 @@ format_search_parameters iterates through the provided params ArrayRef, calling method format_search_parameters => sub { my ($self, $params) = @_; - + my $genparams = []; foreach my $param (@$params) @@ -510,21 +445,21 @@ generate_column_parameters recursively generates properly aliased parameters for { my ($self, $source, $param, $join, $base) = @_; $base ||= 'me'; - my $search_params; + my $search_params = {}; # build up condition foreach my $column (keys %$param) { - if($source->has_relationship($column)) + if ($source->has_relationship($column)) { + # check if the value isn't a hashref unless (ref($param->{$column}) && reftype($param->{$column}) eq 'HASH') { $search_params->{join('.', $base, $column)} = $param->{$column}; next; } - %$search_params = - %{ + $search_params = { %$search_params, %{ $self->generate_column_parameters ( $source->related_source($column), @@ -532,12 +467,24 @@ generate_column_parameters recursively generates properly aliased parameters for Catalyst::Controller::DBIC::API::JoinBuilder->new(parent => $join, name => $column), $column ) - }; + }}; } - else + elsif ($source->has_column($column)) { $search_params->{join('.', $base, $column)} = $param->{$column}; } + # might be a sql function instead of a column name + # e.g. {colname => {like => '%foo%'}} + else + { + # but only if it's not a hashref + unless (ref($param->{$column}) && reftype($param->{$column}) eq 'HASH') { + $search_params->{join('.', $base, $column)} = $param->{$column}; + } + else { + die "$column is neither a relationship nor a column\n"; + } + } } return $search_params; @@ -566,7 +513,7 @@ This builder method generates the search attributes { my ($self, $args) = @_; my $static = $self->_controller; - my $search_attributes = + my $search_attributes = { group_by => $self->grouped_by || ((scalar(@{$static->grouped_by})) ? $static->grouped_by : undef), order_by => $self->ordered_by || ((scalar(@{$static->ordered_by})) ? $static->ordered_by : undef), @@ -574,6 +521,7 @@ This builder method generates the search attributes as => $self->as || ((scalar(@{$static->as})) ? $static->as : undef), prefetch => $self->prefetch || $static->prefetch || undef, rows => $self->count || $static->count, + page => $static->page, offset => $self->offset, join => $self->build_joins, }; @@ -587,15 +535,15 @@ This builder method generates the search attributes $search_attributes->{page} = $search_attributes->{offset} / $search_attributes->{rows} + 1; delete $search_attributes->{offset}; } - - $search_attributes = - { + + $search_attributes = + { map { @$_ } grep { - defined($_->[1]) - ? + defined($_->[1]) + ? (ref($_->[1]) && reftype($_->[1]) eq 'HASH' && keys %{$_->[1]}) || (ref($_->[1]) && reftype($_->[1]) eq 'ARRAY' && @{$_->[1]}) || length($_->[1]) @@ -610,7 +558,7 @@ This builder method generates the search attributes if ($search_attributes->{page} && !$search_attributes->{rows}) { die 'list_page can only be used with list_count'; } - + if ($search_attributes->{select}) { # make sure all columns have an alias to avoid ambiguous issues # but allow non strings (eg. hashrefs for db procs like 'count') @@ -619,7 +567,7 @@ This builder method generates the search attributes } return $search_attributes; - + }; };