bug_update.php: Rework access checking so that the privilege to reopen or close a bug cannot be abused to make arbitrary updates.

While I believe the approach is basically right, this patch is likely to
break some customized setups.

diff --git a/bug_update.php b/bug_update.php
index 1f02d5d..0880570 100644
--- a/bug_update.php
+++ b/bug_update.php
@@ -44,15 +44,8 @@
 		$g_project_override = $t_bug_data->project_id;
 	}
 
-	if ( !(
-				( access_has_bug_level( access_get_status_threshold( $f_new_status, bug_get_field( $f_bug_id, 'project_id' ) ), $f_bug_id ) ) ||
-				( access_has_bug_level( config_get( 'update_bug_threshold' ) , $f_bug_id ) ) ||
-				( ( bug_get_field( $f_bug_id, 'reporter_id' ) == auth_get_current_user_id() ) &&
-						( ( ON == config_get( 'allow_reporter_reopen' ) ) ||
-								( ON == config_get( 'allow_reporter_close' ) ) ) )
-			) ) {
-		access_denied();
-	}
+	# The primary access check is now done below so it can be more closely coupled
+	# to the actual operation being performed.
 
 	# extract current extended information
 	$t_old_bug_status = $t_bug_data->status;
@@ -149,26 +142,38 @@
 			continue;
 		}
 
+		# It's OK to do this before the primary access check, since we did an
+		# access check specifically for this custom field.
 		if ( !custom_field_set_value( $t_id, $f_bug_id, gpc_get_custom_field( "custom_field_$t_id", $t_def['type'], NULL ) ) ) {
 			error_parameters( lang_get_defaulted( custom_field_get_field( $t_id, 'name' ) ) );
 			trigger_error( ERROR_CUSTOM_FIELD_INVALID_VALUE, ERROR );
 		}
 	}
 
-	$t_notify = true;
-	$t_bug_note_set = false;
-	if ( ( $t_old_bug_status != $t_bug_data->status ) && ( FALSE == $f_update_mode ) ) {
+	# Each of the alternatives here does a primary access check.
+	if ( $f_update_mode ) {
+		if ( ! access_has_bug_level( config_get( 'update_bug_threshold' ) , $f_bug_id ) ) {
+			access_denied();
+		}
+
+		$t_bug_data->update( true );
+
+		# Add a bugnote if there is one
+		bugnote_add( $f_bug_id, $f_bugnote_text, $f_time_tracking, $f_private, 0, '', NULL, FALSE );
+	} else {
 		# handle status transitions that come from pages other than bug_*update_page.php
 		# this does the minimum to act on the bug and sends a specific message
 		if ( $t_bug_data->status >= $t_resolved
 			&& $t_bug_data->status < $t_closed
 			&& $t_old_bug_status < $t_resolved ) {
+			if ( ! access_has_bug_level( access_get_status_threshold( $t_resolved, bug_get_field( $f_bug_id, 'project_id' ) ), $f_bug_id ) ) {
+				access_denied();
+			}
+
 			# bug_resolve updates the status, fixed_in_version, resolution, handler_id and bugnote and sends message
 			bug_resolve( $f_bug_id, $t_bug_data->resolution, $t_bug_data->fixed_in_version,
 				$f_bugnote_text, $t_bug_data->duplicate_id, $t_bug_data->handler_id,
 				$f_private, $f_time_tracking );
-			$t_notify = false;
-			$t_bug_note_set = true;
 
 			if ( $f_close_now ) {
 				bug_set_field( $f_bug_id, 'status', $t_closed );
@@ -180,22 +185,17 @@
 			$t_bug_data->status = bug_get_field( $f_bug_id, 'status' );
 		} else if ( $t_bug_data->status >= $t_closed
 			&& $t_old_bug_status < $t_closed ) {
+			access_ensure_can_close_bug( $f_bug_id );
+
 			# bug_close updates the status and bugnote and sends message
 			bug_close( $f_bug_id, $f_bugnote_text, $f_private, $f_time_tracking );
-			$t_notify = false;
-			$t_bug_note_set = true;
 		} else if ( $t_bug_data->status == config_get( 'bug_reopen_status' )
 			&& $t_old_bug_status >= $t_resolved ) {
+			access_ensure_can_reopen_bug( $f_bug_id );
+
 			bug_set_field( $f_bug_id, 'handler_id', $t_bug_data->handler_id ); # fix: update handler_id before calling bug_reopen
 			# bug_reopen updates the status and bugnote and sends message
 			bug_reopen( $f_bug_id, $f_bugnote_text, $f_time_tracking, $f_private );
-			$t_notify = false;
-			$t_bug_note_set = true;
-
-			// update bug data with fields that may be updated inside bug_resolve(), otherwise changes will be overwritten
-			// in bug_update() call below.
-			$t_bug_data->status = bug_get_field( $f_bug_id, 'status' );
-			$t_bug_data->resolution = bug_get_field( $f_bug_id, 'resolution' );
 		}
 	}
 
@@ -205,14 +205,6 @@
 		$t_bug_data = $t_new_bug_data;
 	}
 
-	# Add a bugnote if there is one
-	if ( false == $t_bug_note_set ) {
-		bugnote_add( $f_bug_id, $f_bugnote_text, $f_time_tracking, $f_private, 0, '', NULL, FALSE );
-	}
-
-	# Update the bug entry, notify if we haven't done so already
-	$t_bug_data->update( true, ( false == $t_notify ) );
-
 	form_security_purge( 'bug_update' );
 
 	helper_call_custom_function( 'issue_update_notify', array( $f_bug_id ) );
