@@ -2717,9 +2717,17 @@ sub set_all {
27172717
27182718 $self -> _add_remove($params , ' see_also' );
27192719
2720- # And set custom fields.
2720+ # And set custom fields. Only fields enabled for the bug's current
2721+ # product/component are considered; product/component were already applied
2722+ # via SUPER::set_all above, so this reflects the post-move state. This is
2723+ # intentional (bug 2036191): a value submitted for a field not enabled on
2724+ # the target product/component is ignored rather than written, closing the
2725+ # glitch where e.g. cf_crash_signature could be stored on a product that
2726+ # doesn't expose it.
27212727 my @custom_fields
2722- = grep { $_ -> type != FIELD_TYPE_EXTENSION } Bugzilla-> active_custom_fields;
2728+ = grep { $_ -> type != FIELD_TYPE_EXTENSION } Bugzilla-> active_custom_fields(
2729+ {product => $self -> product_obj, component => $self -> component_obj}
2730+ );
27232731 foreach my $field (@custom_fields ) {
27242732 my $fname = $field -> name;
27252733 if (exists $params -> {$fname }) {
@@ -3054,16 +3062,33 @@ sub _set_product {
30543062 # We copy this array because the original array is modified while we're
30553063 # working, and that confuses "foreach".
30563064 my @current_groups = @{$self -> groups_in};
3065+ my $dropped_group = 0;
30573066 foreach my $group (@current_groups ) {
30583067 if (!$product -> group_is_valid($group )) {
30593068 $self -> remove_group($group );
3069+ $dropped_group = 1;
30603070 }
30613071 }
30623072
30633073 # Make sure the bug is in all the mandatory groups for the new product.
30643074 foreach my $group (@{$product -> groups_mandatory}) {
30653075 $self -> add_group($group );
30663076 }
3077+
3078+ # If any of the bug's groups were dropped because they aren't valid in the
3079+ # new product, add the target product's default security group so the bug
3080+ # stays restricted to the best of our ability. This covers non-default
3081+ # security groups such as dom-core-security, not just the source product's
3082+ # own default (Bug 2028240, Bug 2049554, Bug 2038147). Runs for all usage
3083+ # modes; the browser path additionally pre-selects the group in the
3084+ # verify-new-product UI via _check_default_product_security_group() in the
3085+ # BMO extension.
3086+ my @pre_move_names = map { $_ -> name } @current_groups ;
3087+ for my $sec_group (
3088+ _target_security_group_when_dropping($product , \@pre_move_names , $dropped_group ))
3089+ {
3090+ $self -> add_group($sec_group );
3091+ }
30673092 }
30683093 else {
30693094 # If we didn't change the product, we just die if any of these are invalid.
@@ -4372,20 +4397,45 @@ sub in_group {
43724397}
43734398
43744399# Returns extra group names to add when cloning $self into $target_product.
4375- # If the bug is in its source product's default security group and the target
4376- # product has a different one, the target group name is returned so the clone
4377- # stays secure (Bug 2028240).
4400+ # When cloning across products, any of the bug's groups that aren't valid in
4401+ # the target product are silently dropped. If that would lose a group, we add
4402+ # the target product's default security group so the clone stays restricted to
4403+ # the best of our ability. This mirrors what happens when a bug is moved
4404+ # between products (see Bugzilla::check_default_product_security_group, which
4405+ # the verify-new-product template calls with the dropped groups), and covers
4406+ # non-default security groups such as dom-core-security -- not just the source
4407+ # product's own default (Bug 2028240, Bug 2049554).
43784408sub extra_security_groups_for_clone {
43794409 my ($self , $target_product ) = @_ ;
43804410 return () if $self -> product_id == $target_product -> id;
4411+ return () unless @{$self -> groups_in};
4412+
4413+ # If every group the bug is in carries over to the target product, then no
4414+ # restriction is lost and there is nothing to compensate for.
4415+ my $dropped = $self -> get_invalid_groups(
4416+ {bug_ids => [$self -> id], product => $target_product });
4417+
4418+ my @group_names = map { $_ -> name } @{$self -> groups_in};
4419+ return _target_security_group_when_dropping($target_product , \@group_names ,
4420+ scalar @$dropped );
4421+ }
4422+
4423+ # Returns the target product's default security group name to add when a bug is
4424+ # moved or cloned into $target_product and at least one of its groups would be
4425+ # dropped there ($any_dropped is true). Returns () when nothing is dropped, the
4426+ # target has no default security group, or the bug is already in it.
4427+ # $group_names is an arrayref of the bug's current group names (caller supplies
4428+ # the snapshot it needs). This keeps a bug that was secured in *any* group --
4429+ # not only the source product's own default security group -- restricted after
4430+ # a product change (Bug 2028240, Bug 2049554, Bug 2038147).
4431+ sub _target_security_group_when_dropping {
4432+ my ($target_product , $group_names , $any_dropped ) = @_ ;
4433+ return () unless $any_dropped ;
43814434 return () unless $target_product -> can(' default_security_group' );
43824435
4383- my @clone_groups = map { $_ -> name } @{$self -> groups_in};
4384- my $source_sec = eval { $self -> product_obj-> default_security_group };
4385- return () unless $source_sec && grep { $_ eq $source_sec } @clone_groups ;
4386-
4436+ local $@ ;
43874437 my $target_sec = eval { $target_product -> default_security_group };
4388- return () unless $target_sec && !grep { $_ eq $target_sec } @clone_groups ;
4438+ return () unless $target_sec && !grep { $_ eq $target_sec } @$group_names ;
43894439
43904440 return ($target_sec );
43914441}
0 commit comments