1<?php 2# MantisBT - A PHP based bugtracking system 3 4# MantisBT is free software: you can redistribute it and/or modify 5# it under the terms of the GNU General Public License as published by 6# the Free Software Foundation, either version 2 of the License, or 7# (at your option) any later version. 8# 9# MantisBT is distributed in the hope that it will be useful, 10# but WITHOUT ANY WARRANTY; without even the implied warranty of 11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12# GNU General Public License for more details. 13# 14# You should have received a copy of the GNU General Public License 15# along with MantisBT. If not, see <http://www.gnu.org/licenses/>. 16 17/** 18 * Access API 19 * 20 * @package CoreAPI 21 * @subpackage AccessAPI 22 * @copyright Copyright 2000 - 2002 Kenzaburo Ito - kenito@300baud.org 23 * @copyright Copyright 2002 MantisBT Team - mantisbt-dev@lists.sourceforge.net 24 * @link http://www.mantisbt.org 25 * 26 * @uses authentication_api.php 27 * @uses bug_api.php 28 * @uses bugnote_api.php 29 * @uses config_api.php 30 * @uses constant_inc.php 31 * @uses current_user_api.php 32 * @uses database_api.php 33 * @uses error_api.php 34 * @uses helper_api.php 35 * @uses lang_api.php 36 * @uses print_api.php 37 * @uses project_api.php 38 * @uses string_api.php 39 * @uses user_api.php 40 */ 41 42require_api( 'authentication_api.php' ); 43require_api( 'bug_api.php' ); 44require_api( 'bugnote_api.php' ); 45require_api( 'config_api.php' ); 46require_api( 'constant_inc.php' ); 47require_api( 'current_user_api.php' ); 48require_api( 'database_api.php' ); 49require_api( 'error_api.php' ); 50require_api( 'helper_api.php' ); 51require_api( 'lang_api.php' ); 52require_api( 'print_api.php' ); 53require_api( 'project_api.php' ); 54require_api( 'string_api.php' ); 55require_api( 'user_api.php' ); 56 57use Mantis\Exceptions\ClientException; 58 59# @global array $g_cache_access_matrix 60$g_cache_access_matrix = array(); 61 62# @global array $g_cache_access_matrix_project_ids 63$g_cache_access_matrix_project_ids = array(); 64 65# @global array $g_cache_access_matrix_user_ids 66$g_cache_access_matrix_user_ids = array(); 67 68/** 69 * Function to be called when a user is attempting to access a page that 70 * he/she is not authorised to. This outputs an access denied message then 71 * re-directs to the mainpage. 72 * 73 * @return void 74 */ 75function access_denied() { 76 if( !auth_is_user_authenticated() ) { 77 if( basename( $_SERVER['SCRIPT_NAME'] ) != auth_login_page() ) { 78 $t_return_page = $_SERVER['SCRIPT_NAME']; 79 if( isset( $_SERVER['QUERY_STRING'] ) ) { 80 $t_return_page .= '?' . $_SERVER['QUERY_STRING']; 81 } 82 $t_return_page = string_url( string_sanitize_url( $t_return_page ) ); 83 print_header_redirect( auth_login_page( 'return=' . $t_return_page ) ); 84 } 85 } else { 86 if( current_user_is_anonymous() ) { 87 if( basename( $_SERVER['SCRIPT_NAME'] ) != auth_login_page() ) { 88 $t_return_page = $_SERVER['SCRIPT_NAME']; 89 if( isset( $_SERVER['QUERY_STRING'] ) ) { 90 $t_return_page .= '?' . $_SERVER['QUERY_STRING']; 91 } 92 $t_return_page = string_url( string_sanitize_url( $t_return_page ) ); 93 echo '<p class="center">' . error_string( ERROR_ACCESS_DENIED ) . '</p><p class="center">'; 94 print_link_button( auth_login_page( 'return=' . $t_return_page ), lang_get( 'login' ) ); 95 echo '</p><p class="center">'; 96 print_link_button( 97 helper_mantis_url( config_get_global( 'default_home_page' ) ), 98 lang_get( 'proceed' ) 99 ); 100 echo '</p>'; 101 } 102 } else { 103 layout_page_header(); 104 layout_admin_page_begin(); 105 echo '<div class="space-10"></div>'; 106 html_operation_failure( 107 helper_mantis_url( config_get_global( 'default_home_page' ) ), 108 error_string( ERROR_ACCESS_DENIED ) 109 ); 110 layout_admin_page_end(); 111 } 112 } 113 exit; 114} 115 116/** 117 * retrieves and returns access matrix for a project from cache or caching if required. 118 * @param integer $p_project_id Integer representing project identifier. 119 * @return array returns an array of users->accesslevel for the given user 120 * @access private 121 */ 122function access_cache_matrix_project( $p_project_id ) { 123 global $g_cache_access_matrix, $g_cache_access_matrix_project_ids; 124 125 if( ALL_PROJECTS == (int)$p_project_id ) { 126 return array(); 127 } 128 129 if( !in_array( (int)$p_project_id, $g_cache_access_matrix_project_ids ) ) { 130 db_param_push(); 131 $t_query = 'SELECT user_id, access_level FROM {project_user_list} WHERE project_id=' . db_param(); 132 $t_result = db_query( $t_query, array( (int)$p_project_id ) ); 133 while( $t_row = db_fetch_array( $t_result ) ) { 134 $g_cache_access_matrix[(int)$t_row['user_id']][(int)$p_project_id] = (int)$t_row['access_level']; 135 } 136 137 $g_cache_access_matrix_project_ids[] = (int)$p_project_id; 138 } 139 140 $t_results = array(); 141 142 foreach( $g_cache_access_matrix as $t_user ) { 143 if( isset( $t_user[(int)$p_project_id] ) ) { 144 $t_results[(int)$p_project_id] = $t_user[(int)$p_project_id]; 145 } 146 } 147 148 return $t_results; 149} 150 151/** 152 * retrieves and returns access matrix for a user from cache or caching if required. 153 * @param integer $p_user_id Integer representing user identifier. 154 * @return array returns an array of projects->accesslevel for the given user 155 * @access private 156 */ 157function access_cache_matrix_user( $p_user_id ) { 158 global $g_cache_access_matrix, $g_cache_access_matrix_user_ids; 159 160 if( !in_array( (int)$p_user_id, $g_cache_access_matrix_user_ids ) ) { 161 db_param_push(); 162 $t_query = 'SELECT project_id, access_level FROM {project_user_list} WHERE user_id=' . db_param(); 163 $t_result = db_query( $t_query, array( (int)$p_user_id ) ); 164 165 # make sure we always have an array to return 166 $g_cache_access_matrix[(int)$p_user_id] = array(); 167 168 while( $t_row = db_fetch_array( $t_result ) ) { 169 $g_cache_access_matrix[(int)$p_user_id][(int)$t_row['project_id']] = (int)$t_row['access_level']; 170 } 171 172 $g_cache_access_matrix_user_ids[] = (int)$p_user_id; 173 } 174 175 return $g_cache_access_matrix[(int)$p_user_id]; 176} 177 178/** 179 * Check the a user's access against the given "threshold" and return true 180 * if the user can access, false otherwise. 181 * $p_threshold may be a single value, or an array. If it is a single 182 * value, treat it as a threshold so return true if user is >= threshold. 183 * If it is an array, look for exact matches to one of the values 184 * @param integer $p_user_access_level User access level. 185 * @param integer|array $p_threshold Access threshold, defaults to NOBODY. 186 * @return boolean true or false depending on whether given access level matches the threshold 187 * @access public 188 */ 189function access_compare_level( $p_user_access_level, $p_threshold = NOBODY ) { 190 if( is_array( $p_threshold ) ) { 191 return( in_array( $p_user_access_level, $p_threshold ) ); 192 } else { 193 return( $p_user_access_level >= $p_threshold ); 194 } 195} 196 197/** 198 * This function only checks the user's global access level, ignoring any 199 * overrides they might have at a project level 200 * @param integer|null $p_user_id Integer representing user identifier, defaults to null to use current user. 201 * @return integer global access level 202 * @access public 203 */ 204function access_get_global_level( $p_user_id = null ) { 205 if( $p_user_id === null ) { 206 $p_user_id = auth_get_current_user_id(); 207 } 208 209 # Deal with not logged in silently in this case 210 # @@@ we may be able to remove this and just error 211 # and once we default to anon login, we can remove it for sure 212 if( empty( $p_user_id ) && !auth_is_user_authenticated() ) { 213 return false; 214 } 215 216 return user_get_field( $p_user_id, 'access_level' ); 217} 218 219/** 220 * Check the current user's access against the given value and return true 221 * if the user's access is equal to or higher, false otherwise. 222 * @param integer $p_access_level Integer representing access level. 223 * @param integer|null $p_user_id Integer representing user id, defaults to null to use current user. 224 * @return boolean whether user has access level specified 225 * @access public 226 */ 227function access_has_global_level( $p_access_level, $p_user_id = null ) { 228 # Short circuit the check in this case 229 if( NOBODY == $p_access_level ) { 230 return false; 231 } 232 233 if( $p_user_id === null ) { 234 $p_user_id = auth_get_current_user_id(); 235 } 236 237 $t_access_level = access_get_global_level( $p_user_id ); 238 239 return access_compare_level( $t_access_level, $p_access_level ); 240} 241 242/** 243 * Check if the user has the specified global access level 244 * and deny access to the page if not 245 * @see access_has_global_level 246 * @param integer $p_access_level Integer representing access level. 247 * @param integer|null $p_user_id Integer representing user id, defaults to null to use current user. 248 * @access public 249 * @return void 250 */ 251function access_ensure_global_level( $p_access_level, $p_user_id = null ) { 252 if( !access_has_global_level( $p_access_level, $p_user_id ) ) { 253 access_denied(); 254 } 255} 256 257/** 258 * This function checks the project access level first (for the current project 259 * if none is specified) and if the user is not listed, it falls back on the 260 * user's global access level. 261 * @param integer $p_project_id Integer representing project id to check access against. 262 * @param integer|null $p_user_id Integer representing user id, defaults to null to use current user. 263 * @return integer access level user has to given project 264 * @access public 265 */ 266function access_get_project_level( $p_project_id = null, $p_user_id = null ) { 267 if( null === $p_user_id ) { 268 $p_user_id = auth_get_current_user_id(); 269 } 270 271 # Deal with not logged in silently in this case 272 # @todo we may be able to remove this and just error and once we default to anon login, we can remove it for sure 273 if( empty( $p_user_id ) && !auth_is_user_authenticated() ) { 274 return ANYBODY; 275 } 276 277 if( null === $p_project_id ) { 278 $p_project_id = helper_get_current_project(); 279 } 280 281 $t_global_access_level = access_get_global_level( $p_user_id ); 282 283 if( ALL_PROJECTS == $p_project_id || user_is_administrator( $p_user_id ) ) { 284 return $t_global_access_level; 285 } else { 286 $t_project_access_level = access_get_local_level( $p_user_id, $p_project_id ); 287 $t_project_view_state = project_get_field( $p_project_id, 'view_state' ); 288 289 # Try to use the project access level. 290 # If the user is not listed in the project, then try to fall back 291 # to the global access level 292 if( false === $t_project_access_level ) { 293 # If the project is private and the user isn't listed, then they 294 # must have the private_project_threshold access level to get in. 295 if( VS_PRIVATE == $t_project_view_state ) { 296 if( access_compare_level( $t_global_access_level, config_get( 'private_project_threshold', null, null, ALL_PROJECTS ) ) ) { 297 return $t_global_access_level; 298 } else { 299 return ANYBODY; 300 } 301 } else { 302 # project access not set, but the project is public 303 return $t_global_access_level; 304 } 305 } else { 306 # project specific access was set 307 return $t_project_access_level; 308 } 309 } 310} 311 312/** 313 * Check the current user's access against the given value and return true 314 * if the user's access is equal to or higher, false otherwise. 315 * @param integer|array $p_access_level Threshold representing an access level. 316 * @param integer $p_project_id Integer representing project id to check access against. 317 * @param integer|null $p_user_id Integer representing user id, defaults to null to use current user. 318 * @return boolean whether user has access level specified 319 * @access public 320 */ 321function access_has_project_level( $p_access_level, $p_project_id = null, $p_user_id = null ) { 322 # Short circuit the check in this case 323 if( NOBODY == $p_access_level ) { 324 return false; 325 } 326 327 if( null === $p_user_id ) { 328 $p_user_id = auth_get_current_user_id(); 329 } 330 if( null === $p_project_id ) { 331 $p_project_id = helper_get_current_project(); 332 } 333 334 $t_access_level = access_get_project_level( $p_project_id, $p_user_id ); 335 336 return access_compare_level( $t_access_level, $p_access_level ); 337} 338 339/** 340 * Filters an array of project ids, based on an access level, returning an array 341 * containing only those projects which meet said access level. 342 * An optional limit for the number of results is provided as a shortcut for access checks. 343 * 344 * @param integer|array|string $p_access_level Parameter representing access level threshold, may be: 345 * - integer: for a simple threshold 346 * - array: for an array threshold 347 * - string: for a threshold option which will be evaluated 348 * for each project context 349 * @param array $p_project_ids Array of project ids to check access against, default to null 350 * to use all user accessible projects 351 * @param integer|null $p_user_id Integer representing user id, defaults to null to use current user. 352 * @param integer $p_limit Maximum number of results, default is 0 for all results 353 * @return array The filtered array of project ids 354 */ 355function access_project_array_filter( $p_access_level, array $p_project_ids = null, $p_user_id = null, $p_limit = 0 ) { 356 # Short circuit the check in this case 357 if( NOBODY == $p_access_level ) { 358 return array(); 359 } 360 361 if( null === $p_user_id ) { 362 $p_user_id = auth_get_current_user_id(); 363 } 364 if( null === $p_project_ids ) { 365 $p_project_ids = user_get_all_accessible_projects( $p_user_id ); 366 } 367 368 # Determine if parameter is a configuration string to be evaluated for each project 369 $t_is_config_string = ( is_string( $p_access_level ) && !is_numeric( $p_access_level ) ); 370 371 # if config will be evaluated for each project, prepare a default value 372 if( $t_is_config_string ) { 373 $t_default = config_get( $p_access_level, null, $p_user_id, ALL_PROJECTS ); 374 if( null === $t_default ) { 375 $t_default = config_get_global( $p_access_level ); 376 } 377 } 378 379 $t_check_level = $p_access_level; 380 $t_filtered_projects = array(); 381 foreach( $p_project_ids as $t_project_id ) { 382 # If a config string is provided, evaluate for each project 383 if( $t_is_config_string ) { 384 $t_check_level = config_get( $p_access_level, $t_default, $p_user_id, $t_project_id ); 385 } 386 if( access_has_project_level( $t_check_level, $t_project_id, $p_user_id ) ) { 387 $t_filtered_projects[] = $t_project_id; 388 # Shortcut if the result limit has been reached 389 if( --$p_limit == 0 ) { 390 break; 391 } 392 } 393 } 394 395 return $t_filtered_projects; 396} 397 398/** 399 * Check the current user's access against the given value, in each of the provided projects, 400 * and return true if the user's access is equal to or higher in any of the projects, false otherwise. 401 * @param integer|array|string $p_access_level Parameter representing access level threshold, may be: 402 * - integer: for a simple threshold 403 * - array: for an array threshold 404 * - string: for a threshold option which will be evaluated 405 * for each project context 406 * @param array $p_project_ids Array of project ids to check access against, default to null 407 * to use all user accessible projects 408 * @param integer|null $p_user_id Integer representing user id, defaults to null to use current user. 409 * @return boolean True if user has the specified access level for any of the projects 410 * @access public 411 */ 412function access_has_any_project_level( $p_access_level, array $p_project_ids = null, $p_user_id = null ) { 413 # We only need 1 matching project to return positive 414 $t_matches = access_project_array_filter( $p_access_level, $p_project_ids, $p_user_id, 1 ); 415 return !empty( $t_matches ); 416} 417 418/** 419 * Check if the user has the specified access level in any of the given projects 420 * Refer to access_has_any_project_level() for detailed parameter information 421 * @param integer|array|string $p_access_level Parameter representing access level threshold 422 * @param array $p_project_ids Array of project ids to check access against 423 * @param integer|null $p_user_id 424 */ 425function access_ensure_any_project_level( $p_access_level, array $p_project_ids = null, $p_user_id = null ) { 426 if( !access_has_any_project_level( $p_access_level, $p_project_ids, $p_user_id ) ) { 427 access_denied(); 428 } 429} 430 431/** 432 * Check if the user has the specified access level for the given project 433 * and deny access to the page if not 434 * @see access_has_project_level 435 * @param integer $p_access_level Integer representing access level. 436 * @param integer|null $p_project_id Integer representing project id to check access against, defaults to null to use current project. 437 * @param integer|null $p_user_id Integer representing user identifier, defaults to null to use current user. 438 * @access public 439 * @return void 440 */ 441function access_ensure_project_level( $p_access_level, $p_project_id = null, $p_user_id = null ) { 442 if( !access_has_project_level( $p_access_level, $p_project_id, $p_user_id ) ) { 443 access_denied(); 444 } 445} 446 447/** 448 * Check whether the user has the specified access level for any project project 449 * 450 * Warning: this function may mislead into incorrect validations. Usually you want to 451 * check that a user meets a threshold for any project, but that threshold may be configured 452 * differently for each project, and the user may also have different access levels in each 453 * project due to private projects assignment. 454 * In that scenario, $p_access_level can't be a static threshold, but a "threshold identifier" 455 * instead, that must be evaluated for each project. 456 * Function "access_has_any_project_level()" provides that functionality, also covers the basic 457 * usage of this function. 458 * For such reasons, this function has been deprecated. 459 * 460 * @param integer $p_access_level Integer representing access level. 461 * @param integer|null $p_user_id Integer representing user id, defaults to null to use current user. 462 * @return boolean whether user has access level specified 463 * @access public 464 * @deprecated access_has_any_project_level() should be used in preference to this function (since verrsion 2.6) 465 */ 466function access_has_any_project( $p_access_level, $p_user_id = null ) { 467 error_parameters( __FUNCTION__ . '()', 'access_has_any_project_level()' ); 468 trigger_error( ERROR_DEPRECATED_SUPERSEDED, DEPRECATED ); 469 470 # Short circuit the check in this case 471 if( NOBODY == $p_access_level ) { 472 return false; 473 } 474 475 if( null === $p_user_id ) { 476 $p_user_id = auth_get_current_user_id(); 477 } 478 479 $t_projects = project_get_all_rows(); 480 foreach( $t_projects as $t_project ) { 481 if( access_has_project_level( $p_access_level, $t_project['id'], $p_user_id ) ) { 482 return true; 483 } 484 } 485 486 return false; 487} 488 489/** 490 * Check the current user's access against the given value and return true 491 * if the user's access is equal to or higher, false otherwise. 492 * This function looks up the bug's project and performs an access check 493 * against that project 494 * @param integer $p_access_level Integer representing access level. 495 * @param integer $p_bug_id Integer representing bug id to check access against. 496 * @param integer|null $p_user_id Integer representing user id, defaults to null to use current user. 497 * @return boolean whether user has access level specified 498 * @access public 499 */ 500function access_has_bug_level( $p_access_level, $p_bug_id, $p_user_id = null ) { 501 if( $p_user_id === null ) { 502 $p_user_id = auth_get_current_user_id(); 503 } 504 505 # Deal with not logged in silently in this case 506 # @@@ we may be able to remove this and just error 507 # and once we default to anon login, we can remove it for sure 508 if( empty( $p_user_id ) && !auth_is_user_authenticated() ) { 509 return false; 510 } 511 512 # Check the requested access level, shortcut to fail if not satisfied 513 $t_project_id = bug_get_field( $p_bug_id, 'project_id' ); 514 $t_access_level = access_get_project_level( $t_project_id, $p_user_id ); 515 if( !access_compare_level( $t_access_level, $p_access_level ) ){ 516 return false; 517 } 518 519 # If the level is met, we still need to verify that user has access to the issue 520 521 # Check if the bug is private 522 $t_bug_is_user_reporter = bug_is_user_reporter( $p_bug_id, $p_user_id ); 523 if( !$t_bug_is_user_reporter && bug_get_field( $p_bug_id, 'view_state' ) == VS_PRIVATE ) { 524 $t_private_bug_threshold = config_get( 'private_bug_threshold', null, $p_user_id, $t_project_id ); 525 if( !access_compare_level( $t_access_level, $t_private_bug_threshold ) ) { 526 return false; 527 } 528 } 529 530 # Check special limits 531 # Limited view means this user can only view the issues they reported, is handling, or monitoring 532 if( access_has_limited_view( $t_project_id, $p_user_id ) ) { 533 $t_allowed = $t_bug_is_user_reporter; 534 if( !$t_allowed ) { 535 $t_allowed = bug_is_user_handler( $p_bug_id, $p_user_id ); 536 } 537 if( !$t_allowed ) { 538 $t_allowed = user_is_monitoring_bug( $p_user_id, $p_bug_id ); 539 } 540 if( !$t_allowed ) { 541 return false; 542 } 543 } 544 545 return true; 546} 547 548/** 549 * Filter the provided array of user ids to those who has the specified access level for the 550 * specified bug. 551 * @param int $p_access_level The access level. 552 * @param int $p_bug_id The bug id. 553 * @param array $p_user_ids The array of user ids. 554 * @return array filtered array of user ids. 555 */ 556function access_has_bug_level_filter( $p_access_level, $p_bug_id, $p_user_ids ) { 557 $t_users_ids_with_access = array(); 558 foreach( $p_user_ids as $t_user_id ) { 559 if( access_has_bug_level( $p_access_level, $p_bug_id, $t_user_id ) ) { 560 $t_users_ids_with_access[] = $t_user_id; 561 } 562 } 563 564 return $t_users_ids_with_access; 565} 566 567/** 568 * Check if the user has the specified access level for the given bug 569 * and deny access to the page if not 570 * @see access_has_bug_level 571 * @param integer $p_access_level Integer representing access level. 572 * @param integer $p_bug_id Integer representing bug id to check access against. 573 * @param integer|null $p_user_id Integer representing user id, defaults to null to use current user. 574 * @return void 575 * @access public 576 */ 577function access_ensure_bug_level( $p_access_level, $p_bug_id, $p_user_id = null ) { 578 if( !access_has_bug_level( $p_access_level, $p_bug_id, $p_user_id ) ) { 579 access_denied(); 580 } 581} 582 583/** 584 * Check the current user's access against the given value and return true 585 * if the user's access is equal to or higher, false otherwise. 586 * This function looks up the bugnote's bug and performs an access check 587 * against that bug 588 * @param integer $p_access_level Integer representing access level. 589 * @param integer $p_bugnote_id Integer representing bugnote id to check access against. 590 * @param integer|null $p_user_id Integer representing user id, defaults to null to use current user. 591 * @return boolean whether user has access level specified 592 * @access public 593 */ 594function access_has_bugnote_level( $p_access_level, $p_bugnote_id, $p_user_id = null ) { 595 if( null === $p_user_id ) { 596 $p_user_id = auth_get_current_user_id(); 597 } 598 599 $t_bug_id = bugnote_get_field( $p_bugnote_id, 'bug_id' ); 600 $t_project_id = bug_get_field( $t_bug_id, 'project_id' ); 601 602 # If the bug is private and the user is not the reporter, then the 603 # the user must also have higher access than private_bug_threshold 604 if( bugnote_get_field( $p_bugnote_id, 'view_state' ) == VS_PRIVATE && !bugnote_is_user_reporter( $p_bugnote_id, $p_user_id ) ) { 605 $t_private_bugnote_threshold = config_get( 'private_bugnote_threshold', null, $p_user_id, $t_project_id ); 606 $p_access_level = max( $p_access_level, $t_private_bugnote_threshold ); 607 } 608 609 return access_has_bug_level( $p_access_level, $t_bug_id, $p_user_id ); 610} 611 612/** 613 * Filter the provided array of user ids to those who has the specified access level for the 614 * specified bugnote. 615 * @param int $p_access_level The access level. 616 * @param int $p_bugnote_id The bugnote id. 617 * @param array $p_user_ids The array of user ids. 618 * @return array filtered array of user ids. 619 */ 620function access_has_bugnote_level_filter( $p_access_level, $p_bugnote_id, $p_user_ids ) { 621 $t_users_ids_with_access = array(); 622 foreach( $p_user_ids as $t_user_id ) { 623 if( access_has_bugnote_level( $p_access_level, $p_bugnote_id, $t_user_id ) ) { 624 $t_users_ids_with_access[] = $t_user_id; 625 } 626 } 627 628 return $t_users_ids_with_access; 629} 630 631/** 632 * Check if the user has the specified access level for the given bugnote 633 * and deny access to the page if not 634 * @see access_has_bugnote_level 635 * @param integer $p_access_level Integer representing access level. 636 * @param integer $p_bugnote_id Integer representing bugnote id to check access against. 637 * @param integer|null $p_user_id Integer representing user id, defaults to null to use current user. 638 * @access public 639 * @return void 640 */ 641function access_ensure_bugnote_level( $p_access_level, $p_bugnote_id, $p_user_id = null ) { 642 if( !access_has_bugnote_level( $p_access_level, $p_bugnote_id, $p_user_id ) ) { 643 access_denied(); 644 } 645} 646 647/** 648 * Check if the specified bug can be closed 649 * @param BugData $p_bug Bug to check access against. 650 * @param integer|null $p_user_id Integer representing user id, defaults to null to use current user. 651 * @return boolean true if user can close the bug 652 * @access public 653 */ 654function access_can_close_bug( BugData $p_bug, $p_user_id = null ) { 655 if( bug_is_closed( $p_bug->id ) ) { 656 # Can't close a bug that's already closed 657 return false; 658 } 659 660 if( null === $p_user_id ) { 661 $p_user_id = auth_get_current_user_id(); 662 } 663 664 # If allow_reporter_close is enabled, then reporters can close their own bugs 665 # if they are in resolved status 666 if( ON == config_get( 'allow_reporter_close', null, null, $p_bug->project_id ) 667 && bug_is_user_reporter( $p_bug->id, $p_user_id ) 668 && bug_is_resolved( $p_bug->id ) 669 ) { 670 return true; 671 } 672 673 $t_closed_status = config_get( 'bug_closed_status_threshold', null, null, $p_bug->project_id ); 674 $t_closed_status_threshold = access_get_status_threshold( $t_closed_status, $p_bug->project_id ); 675 return access_has_bug_level( $t_closed_status_threshold, $p_bug->id, $p_user_id ); 676} 677 678/** 679 * Make sure that the user can close the specified bug 680 * @see access_can_close_bug 681 * @param BugData $p_bug Bug to check access against. 682 * @param integer|null $p_user_id Integer representing user id, defaults to null to use current user. 683 * @access public 684 * @return void 685 */ 686function access_ensure_can_close_bug( BugData $p_bug, $p_user_id = null ) { 687 if( !access_can_close_bug( $p_bug, $p_user_id ) ) { 688 access_denied(); 689 } 690} 691 692/** 693 * Check if the specified bug can be reopened 694 * @param BugData $p_bug Bug to check access against. 695 * @param integer|null $p_user_id Integer representing user id, defaults to null to use current user. 696 * @return boolean whether user has access to reopen bugs 697 * @access public 698 */ 699function access_can_reopen_bug( BugData $p_bug, $p_user_id = null ) { 700 if( !bug_is_resolved( $p_bug->id ) ) { 701 # Can't reopen a bug that's not resolved 702 return false; 703 } 704 705 if( $p_user_id === null ) { 706 $p_user_id = auth_get_current_user_id(); 707 } 708 709 $t_reopen_status = config_get( 'bug_reopen_status', null, null, $p_bug->project_id ); 710 711 # Reopen status must be reachable by workflow 712 if( !bug_check_workflow( $p_bug->status, $t_reopen_status ) ) { 713 return false; 714 } 715 716 # If allow_reporter_reopen is enabled, then reporters can always reopen 717 # their own bugs as long as their access level is reporter or above 718 if( ON == config_get( 'allow_reporter_reopen', null, null, $p_bug->project_id ) 719 && bug_is_user_reporter( $p_bug->id, $p_user_id ) 720 && access_has_project_level( config_get( 'report_bug_threshold', null, $p_user_id, $p_bug->project_id ), $p_bug->project_id, $p_user_id ) 721 ) { 722 return true; 723 } 724 725 # Other users's access level must allow them to reopen bugs 726 $t_reopen_bug_threshold = config_get( 'reopen_bug_threshold', null, null, $p_bug->project_id ); 727 if( access_has_bug_level( $t_reopen_bug_threshold, $p_bug->id, $p_user_id ) ) { 728 729 # User must be allowed to change status to reopen status 730 $t_reopen_status_threshold = access_get_status_threshold( $t_reopen_status, $p_bug->project_id ); 731 return access_has_bug_level( $t_reopen_status_threshold, $p_bug->id, $p_user_id ); 732 } 733 734 return false; 735} 736 737/** 738 * Make sure that the user can reopen the specified bug. 739 * Calls access_denied if user has no access to terminate script 740 * @see access_can_reopen_bug 741 * @param BugData $p_bug Bug to check access against. 742 * @param integer|null $p_user_id Integer representing user id, defaults to null to use current user. 743 * @access public 744 * @return void 745 */ 746function access_ensure_can_reopen_bug( BugData $p_bug, $p_user_id = null ) { 747 if( !access_can_reopen_bug( $p_bug, $p_user_id ) ) { 748 access_denied(); 749 } 750} 751 752/** 753 * get the user's access level specific to this project. 754 * return false (0) if the user has no access override here 755 * @param integer $p_user_id Integer representing user id. 756 * @param integer $p_project_id Integer representing project id. 757 * @return boolean|integer returns false (if no access) or an integer representing level of access 758 * @access public 759 */ 760function access_get_local_level( $p_user_id, $p_project_id ) { 761 global $g_cache_access_matrix, $g_cache_access_matrix_project_ids; 762 763 $p_project_id = (int)$p_project_id; 764 $p_user_id = (int)$p_user_id; 765 766 if( in_array( $p_project_id, $g_cache_access_matrix_project_ids ) ) { 767 if( isset( $g_cache_access_matrix[$p_user_id][$p_project_id] ) ) { 768 return $g_cache_access_matrix[$p_user_id][$p_project_id]; 769 } else { 770 return false; 771 } 772 } 773 774 $t_project_level = access_cache_matrix_user( $p_user_id ); 775 776 if( isset( $t_project_level[$p_project_id] ) ) { 777 return $t_project_level[$p_project_id]; 778 } else { 779 return false; 780 } 781} 782 783/** 784 * get the access level required to change the issue to the new status 785 * If there is no specific differentiated access level, use the 786 * generic update_bug_status_threshold. 787 * @param integer $p_status Status. 788 * @param integer $p_project_id Default value ALL_PROJECTS. 789 * @return integer integer representing user level e.g. DEVELOPER 790 * @access public 791 */ 792function access_get_status_threshold( $p_status, $p_project_id = ALL_PROJECTS ) { 793 $t_thresh_array = config_get( 'set_status_threshold', null, null, $p_project_id ); 794 if( isset( $t_thresh_array[(int)$p_status] ) ) { 795 return (int)$t_thresh_array[(int)$p_status]; 796 } else { 797 if( $p_status == config_get( 'bug_submit_status', null, null, $p_project_id ) ) { 798 return config_get( 'report_bug_threshold', null, null, $p_project_id ); 799 } else { 800 return config_get( 'update_bug_status_threshold', null, null, $p_project_id ); 801 } 802 } 803} 804 805/** 806 * Given a access level, return the appropriate string for it 807 * @param integer $p_access_level 808 * @return string 809 */ 810function access_level_get_string( $p_access_level ) { 811 if( $p_access_level > ANYBODY ) { 812 $t_access_level_string = get_enum_element( 'access_levels', $p_access_level ); 813 } else { 814 $t_access_level_string = lang_get( 'no_access' ); 815 } 816 return $t_access_level_string; 817} 818 819/** 820 * Return the minimum access level, as integer, that matches the threshold. 821 * $p_threshold may be a single value, or an array. If it is a single 822 * value, returns that number. If it is an array, return the value of the 823 * smallest element 824 * @param integer|array $p_threshold Access threshold 825 * @return integer Integer value for an access level. 826 */ 827function access_threshold_min_level( $p_threshold ) { 828 if( is_array( $p_threshold ) ) { 829 if( empty( $p_threshold ) ) { 830 return NOBODY; 831 } else { 832 sort( $p_threshold ); 833 return( reset( $p_threshold ) ); 834 } 835 } else { 836 return $p_threshold; 837 } 838 839} 840 841/** 842 * Checks if the user can view the handler for the bug. 843 * @param BugData $p_bug Bug to check access against. 844 * @param integer|null $p_user_id Integer representing user id, defaults to null to use current user. 845 * @return boolean whether user can view the handler user. 846 */ 847function access_can_see_handler_for_bug( BugData $p_bug, $p_user_id = null ) { 848 if( null === $p_user_id ) { 849 $t_user_id = auth_get_current_user_id(); 850 } else { 851 $t_user_id = $p_user_id; 852 } 853 854 # handler can be viewed if allowed by access level, OR the user himself is the handler 855 $t_can_view_handler = 856 ( $p_bug->handler_id == $t_user_id ) 857 || access_has_bug_level( 858 config_get( 'view_handler_threshold', null, $t_user_id, $p_bug->project_id ), 859 $p_bug->id ); 860 861 return $t_can_view_handler; 862} 863 864/** 865 * Parse access level reference array parsed from json. 866 * 867 * @param array $p_access The access level 868 * @return integer The access level 869 * @throws ClientException Access level is invalid or not specified. 870 */ 871function access_parse_array( array $p_access ) { 872 $t_access_levels_enum = config_get( 'access_levels_enum_string' ); 873 $t_access_level = false; 874 875 if( isset( $p_access['id'] ) ) { 876 $t_access_level = (int)$p_access['id']; 877 878 # Make sure the provided id is valid 879 if( !MantisEnum::hasValue( $t_access_levels_enum, $t_access_level ) ) { 880 $t_access_level = false; 881 } 882 } 883 884 if( isset( $p_access['name'] ) ) { 885 $t_access_level = MantisEnum::getValue( $t_access_levels_enum, $p_access['name'] ); 886 } 887 888 if( $t_access_level === false ) { 889 throw new ClientException( 890 'Invalid access level', 891 ERROR_INVALID_FIELD_VALUE, 892 array( 'access_level' ) ); 893 } 894 895 return $t_access_level; 896} 897 898/** 899 * Returns true if the user has limited view to issues in the specified project. 900 * 901 * @param integer $p_project_id Project id, or null for current project 902 * @param integer $p_user_id User id, or null for current user 903 * @return boolean Whether limited view applies 904 * 905 * @see $g_limit_view_unless_threshold 906 * @see $g_limit_reporters 907 */ 908function access_has_limited_view( $p_project_id = null, $p_user_id = null ) { 909 $t_user_id = ( null === $p_user_id ) ? auth_get_current_user_id() : $p_user_id; 910 $t_project_id = ( null === $p_project_id ) ? helper_get_current_project() : $p_project_id; 911 912 # Old 'limit_reporters' option was previously only supported for ALL_PROJECTS, 913 # Use this option if set, otherwise, check the new option for "unlimited view" threshold 914 $t_old_limit_reporters = config_get( 'limit_reporters', null, $t_user_id, ALL_PROJECTS ); 915 $t_threshold_can_view = NOBODY; 916 if( ON != $t_old_limit_reporters ) { 917 $t_threshold_can_view = config_get( 'limit_view_unless_threshold', null, $t_user_id, $t_project_id ); 918 } else { 919 # If old 'limit_reporters' option is enabled, use that setting 920 # Note that the effective threshold can vary for each project, based on 921 # the reporting threshold configuration. 922 # To improve performance, esp. when processing for several projects, we 923 # build a static array holding that threshold for each project 924 static $s_thresholds = array(); 925 if( !isset( $s_thresholds[$t_project_id] ) ) { 926 $t_report_bug_threshold = config_get( 'report_bug_threshold', null, $t_user_id, $t_project_id ); 927 if( empty( $t_report_bug_threshold ) ) { 928 $s_thresholds[$t_project_id] = NOBODY; 929 } else { 930 $s_thresholds[$t_project_id] = access_threshold_min_level( $t_report_bug_threshold ) + 1; 931 } 932 } 933 $t_threshold_can_view = $s_thresholds[$t_project_id]; 934 } 935 936 $t_project_level = access_get_project_level( $p_project_id, $p_user_id ); 937 return !access_compare_level( $t_project_level, $t_threshold_can_view ); 938} 939 940/** 941 * Return true if user is allowed to view bug revisions. 942 * 943 * User must have $g_bug_revision_view_threshold or be the bug's reporter. 944 * 945 * @param int $p_bug_id 946 * @param int $p_user_id 947 * 948 * @return bool 949 */ 950function access_can_view_bug_revisions( $p_bug_id, $p_user_id = null ) { 951 if( !bug_exists( $p_bug_id ) ) { 952 return false; 953 } 954 $t_project_id = bug_get_field( $p_bug_id, 'project_id' ); 955 $t_user_id = null === $p_user_id ? auth_get_current_user_id() : $p_user_id; 956 957 $t_has_access = access_has_bug_level( 958 config_get( 'bug_revision_view_threshold', null, $t_user_id, $t_project_id ), 959 $p_bug_id, 960 $t_user_id 961 ); 962 963 return $t_has_access || bug_is_user_reporter( $p_bug_id, $t_user_id ); 964} 965 966/** 967 * Return true if user is allowed to view bugnote revisions. 968 * 969 * User must have $g_bug_revision_view_threshold or be the bugnote's reporter. 970 * 971 * @param int $p_bugnote_id 972 * @param int $p_user_id 973 * 974 * @return bool 975 */ 976function access_can_view_bugnote_revisions( $p_bugnote_id, $p_user_id = null ) { 977 if( !bugnote_exists( $p_bugnote_id ) ) { 978 return false; 979 } 980 $t_bug_id = bugnote_get_field( $p_bugnote_id, 'bug_id' ); 981 $t_project_id = bug_get_field( $t_bug_id, 'project_id' ); 982 $t_user_id = null === $p_user_id ? auth_get_current_user_id() : $p_user_id; 983 984 $t_has_access = access_has_bugnote_level( 985 config_get( 'bug_revision_view_threshold', null, $t_user_id, $t_project_id ), 986 $p_bugnote_id, 987 $t_user_id 988 ); 989 990 991 return $t_has_access || bugnote_is_user_reporter( $p_bugnote_id, $t_user_id ); 992} 993