summaryrefslogtreecommitdiffstats
path: root/Bugzilla/WebService/Util.pm
diff options
context:
space:
mode:
Diffstat (limited to 'Bugzilla/WebService/Util.pm')
-rw-r--r--Bugzilla/WebService/Util.pm474
1 files changed, 241 insertions, 233 deletions
diff --git a/Bugzilla/WebService/Util.pm b/Bugzilla/WebService/Util.pm
index ce5586911..933d1b5f7 100644
--- a/Bugzilla/WebService/Util.pm
+++ b/Bugzilla/WebService/Util.pm
@@ -29,284 +29,292 @@ use base qw(Exporter);
require Test::Taint if ${^TAINT};
our @EXPORT_OK = qw(
- extract_flags
- filter
- filter_wants
- taint_data
- validate
- translate
- params_to_objects
- fix_credentials
+ extract_flags
+ filter
+ filter_wants
+ taint_data
+ validate
+ translate
+ params_to_objects
+ fix_credentials
);
sub extract_flags {
- my ($flags, $bug, $attachment) = @_;
- my (@new_flags, @old_flags);
+ my ($flags, $bug, $attachment) = @_;
+ my (@new_flags, @old_flags);
- my $flag_types = $attachment ? $attachment->flag_types : $bug->flag_types;
- my $current_flags = $attachment ? $attachment->flags : $bug->flags;
+ my $flag_types = $attachment ? $attachment->flag_types : $bug->flag_types;
+ my $current_flags = $attachment ? $attachment->flags : $bug->flags;
- # Copy the user provided $flags as we may call extract_flags more than
- # once when editing multiple bugs or attachments.
- my $flags_copy = dclone($flags);
+ # Copy the user provided $flags as we may call extract_flags more than
+ # once when editing multiple bugs or attachments.
+ my $flags_copy = dclone($flags);
- foreach my $flag (@$flags_copy) {
- my $id = $flag->{id};
- my $type_id = $flag->{type_id};
+ foreach my $flag (@$flags_copy) {
+ my $id = $flag->{id};
+ my $type_id = $flag->{type_id};
- my $new = delete $flag->{new};
- my $name = delete $flag->{name};
+ my $new = delete $flag->{new};
+ my $name = delete $flag->{name};
- if ($id) {
- my $flag_obj = grep($id == $_->id, @$current_flags);
- $flag_obj || ThrowUserError('object_does_not_exist',
- { class => 'Bugzilla::Flag', id => $id });
- }
- elsif ($type_id) {
- my $type_obj = grep($type_id == $_->id, @$flag_types);
- $type_obj || ThrowUserError('object_does_not_exist',
- { class => 'Bugzilla::FlagType', id => $type_id });
- if (!$new) {
- my @flag_matches = grep($type_id == $_->type->id, @$current_flags);
- @flag_matches > 1 && ThrowUserError('flag_not_unique',
- { value => $type_id });
- if (!@flag_matches) {
- delete $flag->{id};
- }
- else {
- delete $flag->{type_id};
- $flag->{id} = $flag_matches[0]->id;
- }
- }
+ if ($id) {
+ my $flag_obj = grep($id == $_->id, @$current_flags);
+ $flag_obj
+ || ThrowUserError('object_does_not_exist',
+ {class => 'Bugzilla::Flag', id => $id});
+ }
+ elsif ($type_id) {
+ my $type_obj = grep($type_id == $_->id, @$flag_types);
+ $type_obj
+ || ThrowUserError('object_does_not_exist',
+ {class => 'Bugzilla::FlagType', id => $type_id});
+ if (!$new) {
+ my @flag_matches = grep($type_id == $_->type->id, @$current_flags);
+ @flag_matches > 1 && ThrowUserError('flag_not_unique', {value => $type_id});
+ if (!@flag_matches) {
+ delete $flag->{id};
}
- elsif ($name) {
- my @type_matches = grep($name eq $_->name, @$flag_types);
- @type_matches > 1 && ThrowUserError('flag_type_not_unique',
- { value => $name });
- @type_matches || ThrowUserError('object_does_not_exist',
- { class => 'Bugzilla::FlagType', name => $name });
- if ($new) {
- delete $flag->{id};
- $flag->{type_id} = $type_matches[0]->id;
- }
- else {
- my @flag_matches = grep($name eq $_->type->name, @$current_flags);
- @flag_matches > 1 && ThrowUserError('flag_not_unique', { value => $name });
- if (@flag_matches) {
- $flag->{id} = $flag_matches[0]->id;
- }
- else {
- delete $flag->{id};
- $flag->{type_id} = $type_matches[0]->id;
- }
- }
+ else {
+ delete $flag->{type_id};
+ $flag->{id} = $flag_matches[0]->id;
}
-
- if ($flag->{id}) {
- push(@old_flags, $flag);
+ }
+ }
+ elsif ($name) {
+ my @type_matches = grep($name eq $_->name, @$flag_types);
+ @type_matches > 1 && ThrowUserError('flag_type_not_unique', {value => $name});
+ @type_matches
+ || ThrowUserError('object_does_not_exist',
+ {class => 'Bugzilla::FlagType', name => $name});
+ if ($new) {
+ delete $flag->{id};
+ $flag->{type_id} = $type_matches[0]->id;
+ }
+ else {
+ my @flag_matches = grep($name eq $_->type->name, @$current_flags);
+ @flag_matches > 1 && ThrowUserError('flag_not_unique', {value => $name});
+ if (@flag_matches) {
+ $flag->{id} = $flag_matches[0]->id;
}
else {
- push(@new_flags, $flag);
+ delete $flag->{id};
+ $flag->{type_id} = $type_matches[0]->id;
}
+ }
}
- return (\@old_flags, \@new_flags);
+ if ($flag->{id}) {
+ push(@old_flags, $flag);
+ }
+ else {
+ push(@new_flags, $flag);
+ }
+ }
+
+ return (\@old_flags, \@new_flags);
}
sub filter($$;$$) {
- my ($params, $hash, $types, $prefix) = @_;
- my %newhash = %$hash;
+ my ($params, $hash, $types, $prefix) = @_;
+ my %newhash = %$hash;
- foreach my $key (keys %$hash) {
- delete $newhash{$key} if !filter_wants($params, $key, $types, $prefix);
- }
+ foreach my $key (keys %$hash) {
+ delete $newhash{$key} if !filter_wants($params, $key, $types, $prefix);
+ }
- return \%newhash;
+ return \%newhash;
}
sub filter_wants($$;$$) {
- my ($params, $field, $types, $prefix) = @_;
-
- # Since this is operation is resource intensive, we will cache the results
- # This assumes that $params->{*_fields} doesn't change between calls
- my $cache = Bugzilla->request_cache->{filter_wants} ||= {};
- $field = "${prefix}.${field}" if $prefix;
-
- if (exists $cache->{$field}) {
- return $cache->{$field};
- }
-
- # Mimic old behavior if no types provided
- my %field_types = map { $_ => 1 } (ref $types ? @$types : ($types || 'default'));
-
- my %include = map { $_ => 1 } @{ $params->{'include_fields'} || [] };
- my %exclude = map { $_ => 1 } @{ $params->{'exclude_fields'} || [] };
-
- my %include_types;
- my %exclude_types;
-
- # Only return default fields if nothing is specified
- $include_types{default} = 1 if !%include;
-
- # Look for any field types requested
- foreach my $key (keys %include) {
- next if $key !~ /^_(.*)$/;
- $include_types{$1} = 1;
- delete $include{$key};
- }
- foreach my $key (keys %exclude) {
- next if $key !~ /^_(.*)$/;
- $exclude_types{$1} = 1;
- delete $exclude{$key};
- }
-
- # Explicit inclusion/exclusion
- return $cache->{$field} = 0 if $exclude{$field};
- return $cache->{$field} = 1 if $include{$field};
-
- # If the user has asked to include all or exclude all
- return $cache->{$field} = 0 if $exclude_types{'all'};
- return $cache->{$field} = 1 if $include_types{'all'};
-
- # If the user has not asked for any fields specifically or if the user has asked
- # for one or more of the field's types (and not excluded them)
- foreach my $type (keys %field_types) {
- return $cache->{$field} = 0 if $exclude_types{$type};
- return $cache->{$field} = 1 if $include_types{$type};
- }
-
- my $wants = 0;
- if ($prefix) {
- # Include the field if the parent is include (and this one is not excluded)
- $wants = 1 if $include{$prefix};
- }
- else {
- # We want to include this if one of the sub keys is included
- my $key = $field . '.';
- my $len = length($key);
- $wants = 1 if grep { substr($_, 0, $len) eq $key } keys %include;
- }
-
- return $cache->{$field} = $wants;
+ my ($params, $field, $types, $prefix) = @_;
+
+ # Since this is operation is resource intensive, we will cache the results
+ # This assumes that $params->{*_fields} doesn't change between calls
+ my $cache = Bugzilla->request_cache->{filter_wants} ||= {};
+ $field = "${prefix}.${field}" if $prefix;
+
+ if (exists $cache->{$field}) {
+ return $cache->{$field};
+ }
+
+ # Mimic old behavior if no types provided
+ my %field_types
+ = map { $_ => 1 } (ref $types ? @$types : ($types || 'default'));
+
+ my %include = map { $_ => 1 } @{$params->{'include_fields'} || []};
+ my %exclude = map { $_ => 1 } @{$params->{'exclude_fields'} || []};
+
+ my %include_types;
+ my %exclude_types;
+
+ # Only return default fields if nothing is specified
+ $include_types{default} = 1 if !%include;
+
+ # Look for any field types requested
+ foreach my $key (keys %include) {
+ next if $key !~ /^_(.*)$/;
+ $include_types{$1} = 1;
+ delete $include{$key};
+ }
+ foreach my $key (keys %exclude) {
+ next if $key !~ /^_(.*)$/;
+ $exclude_types{$1} = 1;
+ delete $exclude{$key};
+ }
+
+ # Explicit inclusion/exclusion
+ return $cache->{$field} = 0 if $exclude{$field};
+ return $cache->{$field} = 1 if $include{$field};
+
+ # If the user has asked to include all or exclude all
+ return $cache->{$field} = 0 if $exclude_types{'all'};
+ return $cache->{$field} = 1 if $include_types{'all'};
+
+ # If the user has not asked for any fields specifically or if the user has asked
+ # for one or more of the field's types (and not excluded them)
+ foreach my $type (keys %field_types) {
+ return $cache->{$field} = 0 if $exclude_types{$type};
+ return $cache->{$field} = 1 if $include_types{$type};
+ }
+
+ my $wants = 0;
+ if ($prefix) {
+
+ # Include the field if the parent is include (and this one is not excluded)
+ $wants = 1 if $include{$prefix};
+ }
+ else {
+ # We want to include this if one of the sub keys is included
+ my $key = $field . '.';
+ my $len = length($key);
+ $wants = 1 if grep { substr($_, 0, $len) eq $key } keys %include;
+ }
+
+ return $cache->{$field} = $wants;
}
sub taint_data {
- my @params = @_;
- return if !@params;
- # Though this is a private function, it hasn't changed since 2004 and
- # should be safe to use, and prevents us from having to write it ourselves
- # or require another module to do it.
- if (${^TAINT}) {
- Test::Taint::_deeply_traverse(\&_delete_bad_keys, \@params);
- Test::Taint::taint_deeply(\@params);
- }
+ my @params = @_;
+ return if !@params;
+
+ # Though this is a private function, it hasn't changed since 2004 and
+ # should be safe to use, and prevents us from having to write it ourselves
+ # or require another module to do it.
+ if (${^TAINT}) {
+ Test::Taint::_deeply_traverse(\&_delete_bad_keys, \@params);
+ Test::Taint::taint_deeply(\@params);
+ }
}
sub _delete_bad_keys {
- foreach my $item (@_) {
- next if ref $item ne 'HASH';
- foreach my $key (keys %$item) {
- # Making something a hash key always untaints it, in Perl.
- # However, we need to validate our argument names in some way.
- # We know that all hash keys passed in to the WebService will
- # match \w+, so we delete any key that doesn't match that.
- if ($key !~ /^[\w\.\-]+$/) {
- delete $item->{$key};
- }
- }
+ foreach my $item (@_) {
+ next if ref $item ne 'HASH';
+ foreach my $key (keys %$item) {
+
+ # Making something a hash key always untaints it, in Perl.
+ # However, we need to validate our argument names in some way.
+ # We know that all hash keys passed in to the WebService will
+ # match \w+, so we delete any key that doesn't match that.
+ if ($key !~ /^[\w\.\-]+$/) {
+ delete $item->{$key};
+ }
}
- return @_;
+ }
+ return @_;
}
-sub validate {
- my ($self, $params, @keys) = @_;
- my $cache_key = join('|', (caller(1))[3], sort @keys);
- # Type->of() is the same as Type[], used here because it is easier
- # to chain with plus_coercions.
- state $array_of_nonrefs = ArrayRef->of(Maybe[Value])->plus_coercions(
- Maybe[Value], q{ [ $_ ] },
- );
- state $type_cache = {};
- my $params_type = $type_cache->{$cache_key} //= do {
- my %fields = map { $_ => Optional[$array_of_nonrefs] } @keys;
- Maybe[ Dict[%fields, slurpy Any] ];
- };
-
- # If $params is defined but not a reference, then we weren't
- # sent any parameters at all, and we're getting @keys where
- # $params should be.
- return ($self, undef) if (defined $params and !ref $params);
-
- # If @keys is not empty then we convert any named
- # parameters that have scalar values to arrayrefs
- # that match.
- $params = $params_type->coerce($params);
- if (my $type_error = $params_type->validate($params)) {
- FATAL("validate() found type error: $type_error");
- ThrowUserError('invalid_params', { type_error => $type_error } ) if $type_error;
- }
-
- return ($self, $params);
+sub validate {
+ my ($self, $params, @keys) = @_;
+ my $cache_key = join('|', (caller(1))[3], sort @keys);
+
+ # Type->of() is the same as Type[], used here because it is easier
+ # to chain with plus_coercions.
+ state $array_of_nonrefs
+ = ArrayRef->of(Maybe [Value])->plus_coercions(Maybe [Value], q{ [ $_ ] },);
+ state $type_cache = {};
+ my $params_type = $type_cache->{$cache_key} //= do {
+ my %fields = map { $_ => Optional [$array_of_nonrefs] } @keys;
+ Maybe [Dict [%fields, slurpy Any]];
+ };
+
+ # If $params is defined but not a reference, then we weren't
+ # sent any parameters at all, and we're getting @keys where
+ # $params should be.
+ return ($self, undef) if (defined $params and !ref $params);
+
+ # If @keys is not empty then we convert any named
+ # parameters that have scalar values to arrayrefs
+ # that match.
+ $params = $params_type->coerce($params);
+ if (my $type_error = $params_type->validate($params)) {
+ FATAL("validate() found type error: $type_error");
+ ThrowUserError('invalid_params', {type_error => $type_error}) if $type_error;
+ }
+
+ return ($self, $params);
}
sub translate {
- my ($params, $mapped) = @_;
- my %changes;
- while (my ($key,$value) = each (%$params)) {
- my $new_field = $mapped->{$key} || $key;
- $changes{$new_field} = $value;
- }
- return \%changes;
+ my ($params, $mapped) = @_;
+ my %changes;
+ while (my ($key, $value) = each(%$params)) {
+ my $new_field = $mapped->{$key} || $key;
+ $changes{$new_field} = $value;
+ }
+ return \%changes;
}
sub params_to_objects {
- my ($params, $class) = @_;
- my (@objects, @objects_by_ids);
+ my ($params, $class) = @_;
+ my (@objects, @objects_by_ids);
- @objects = map { $class->check($_) }
- @{ $params->{names} } if $params->{names};
+ @objects = map { $class->check($_) } @{$params->{names}} if $params->{names};
- @objects_by_ids = map { $class->check({ id => $_ }) }
- @{ $params->{ids} } if $params->{ids};
+ @objects_by_ids = map { $class->check({id => $_}) } @{$params->{ids}}
+ if $params->{ids};
- push(@objects, @objects_by_ids);
- my %seen;
- @objects = grep { !$seen{$_->id}++ } @objects;
- return \@objects;
+ push(@objects, @objects_by_ids);
+ my %seen;
+ @objects = grep { !$seen{$_->id}++ } @objects;
+ return \@objects;
}
sub fix_credentials {
- my ($params, $cgi) = @_;
-
- # Allow user to pass in authentication details in X-Headers
- # This allows callers to keep credentials out of GET request query-strings
- if ($cgi) {
- foreach my $field (keys %{ API_AUTH_HEADERS() }) {
- next if exists $params->{API_AUTH_HEADERS->{$field}} || ($cgi->http($field) // '') eq '';
- $params->{API_AUTH_HEADERS->{$field}} = uri_unescape($cgi->http($field));
- }
+ my ($params, $cgi) = @_;
+
+ # Allow user to pass in authentication details in X-Headers
+ # This allows callers to keep credentials out of GET request query-strings
+ if ($cgi) {
+ foreach my $field (keys %{API_AUTH_HEADERS()}) {
+ next
+ if exists $params->{API_AUTH_HEADERS->{$field}}
+ || ($cgi->http($field) // '') eq '';
+ $params->{API_AUTH_HEADERS->{$field}} = uri_unescape($cgi->http($field));
}
-
- # Allow user to pass in login=foo&password=bar as a convenience
- # even if not calling GET /login. We also do not delete them as
- # GET /login requires "login" and "password".
- if (exists $params->{'login'} && exists $params->{'password'}) {
- $params->{'Bugzilla_login'} = delete $params->{'login'};
- $params->{'Bugzilla_password'} = delete $params->{'password'};
- }
- # Allow user to pass api_key=12345678 as a convenience which becomes
- # "Bugzilla_api_key" which is what the auth code looks for.
- if (exists $params->{api_key}) {
- $params->{Bugzilla_api_key} = delete $params->{api_key};
- }
- # Allow user to pass token=12345678 as a convenience which becomes
- # "Bugzilla_token" which is what the auth code looks for.
- if (exists $params->{'token'}) {
- $params->{'Bugzilla_token'} = delete $params->{'token'};
- }
-
- # Allow extensions to modify the credential data before login
- Bugzilla::Hook::process('webservice_fix_credentials', { params => $params });
+ }
+
+ # Allow user to pass in login=foo&password=bar as a convenience
+ # even if not calling GET /login. We also do not delete them as
+ # GET /login requires "login" and "password".
+ if (exists $params->{'login'} && exists $params->{'password'}) {
+ $params->{'Bugzilla_login'} = delete $params->{'login'};
+ $params->{'Bugzilla_password'} = delete $params->{'password'};
+ }
+
+ # Allow user to pass api_key=12345678 as a convenience which becomes
+ # "Bugzilla_api_key" which is what the auth code looks for.
+ if (exists $params->{api_key}) {
+ $params->{Bugzilla_api_key} = delete $params->{api_key};
+ }
+
+ # Allow user to pass token=12345678 as a convenience which becomes
+ # "Bugzilla_token" which is what the auth code looks for.
+ if (exists $params->{'token'}) {
+ $params->{'Bugzilla_token'} = delete $params->{'token'};
+ }
+
+ # Allow extensions to modify the credential data before login
+ Bugzilla::Hook::process('webservice_fix_credentials', {params => $params});
}
__END__