1<?php 2// Copyright (C) 2010-2013 Combodo SARL 3// 4// This file is part of iTop. 5// 6// iTop is free software; you can redistribute it and/or modify 7// it under the terms of the GNU Affero General Public License as published by 8// the Free Software Foundation, either version 3 of the License, or 9// (at your option) any later version. 10// 11// iTop is distributed in the hope that it will be useful, 12// but WITHOUT ANY WARRANTY; without even the implied warranty of 13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14// GNU Affero General Public License for more details. 15// 16// You should have received a copy of the GNU Affero General Public License 17// along with iTop. If not, see <http://www.gnu.org/licenses/> 18 19/** 20 * UserRightsProfile 21 * User management Module, basing the right on profiles and a matrix (similar to UserRightsMatrix, but profiles and other decorations have been added) 22 * 23 * @copyright Copyright (C) 2010-2012 Combodo SARL 24 * @license http://opensource.org/licenses/AGPL-3.0 25 */ 26 27define('ADMIN_PROFILE_NAME', 'Administrator'); 28define('PORTAL_PROFILE_NAME', 'Portal user'); 29 30class UserRightsBaseClassGUI extends cmdbAbstractObject 31{ 32 // Whenever something changes, reload the privileges 33 34 protected function AfterInsert() 35 { 36 UserRights::FlushPrivileges(); 37 } 38 39 protected function AfterUpdate() 40 { 41 UserRights::FlushPrivileges(); 42 } 43 44 protected function AfterDelete() 45 { 46 UserRights::FlushPrivileges(); 47 } 48} 49 50class UserRightsBaseClass extends DBObject 51{ 52 // Whenever something changes, reload the privileges 53 54 protected function AfterInsert() 55 { 56 UserRights::FlushPrivileges(); 57 } 58 59 protected function AfterUpdate() 60 { 61 UserRights::FlushPrivileges(); 62 } 63 64 protected function AfterDelete() 65 { 66 UserRights::FlushPrivileges(); 67 } 68} 69 70 71 72 73class URP_Profiles extends UserRightsBaseClassGUI 74{ 75 public static function Init() 76 { 77 $aParams = array 78 ( 79 "category" => "addon/userrights", 80 "key_type" => "autoincrement", 81 "name_attcode" => "name", 82 "state_attcode" => "", 83 "reconc_keys" => array(), 84 "db_table" => "priv_urp_profiles", 85 "db_key_field" => "id", 86 "db_finalclass_field" => "", 87 "display_template" => "", 88 ); 89 MetaModel::Init_Params($aParams); 90 //MetaModel::Init_InheritAttributes(); 91 MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); 92 MetaModel::Init_AddAttribute(new AttributeString("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); 93 94 MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("user_list", array("linked_class"=>"URP_UserProfile", "ext_key_to_me"=>"profileid", "ext_key_to_remote"=>"userid", "allowed_values"=>null, "count_min"=>1, "count_max"=>0, "depends_on"=>array()))); 95 96 // Display lists 97 MetaModel::Init_SetZListItems('details', array('name', 'description', 'user_list')); // Attributes to be displayed for the complete details 98 MetaModel::Init_SetZListItems('list', array('description')); // Attributes to be displayed for a list 99 // Search criteria 100 MetaModel::Init_SetZListItems('standard_search', array('name', 'description')); // Criteria of the std search form 101 MetaModel::Init_SetZListItems('default_search', array ('name', 'description')); 102 } 103 104 protected $m_bCheckReservedNames = true; 105 protected function DisableCheckOnReservedNames() 106 { 107 $this->m_bCheckReservedNames = false; 108 } 109 110 111 protected static $m_aActions = array( 112 UR_ACTION_READ => 'Read', 113 UR_ACTION_MODIFY => 'Modify', 114 UR_ACTION_DELETE => 'Delete', 115 UR_ACTION_BULK_READ => 'Bulk Read', 116 UR_ACTION_BULK_MODIFY => 'Bulk Modify', 117 UR_ACTION_BULK_DELETE => 'Bulk Delete', 118 ); 119 120 protected static $m_aCacheActionGrants = null; 121 protected static $m_aCacheStimulusGrants = null; 122 protected static $m_aCacheProfiles = null; 123 124 public static function DoCreateProfile($sName, $sDescription, $bReservedName = false) 125 { 126 if (is_null(self::$m_aCacheProfiles)) 127 { 128 self::$m_aCacheProfiles = array(); 129 $oFilterAll = new DBObjectSearch('URP_Profiles'); 130 $oSet = new DBObjectSet($oFilterAll); 131 while ($oProfile = $oSet->Fetch()) 132 { 133 self::$m_aCacheProfiles[$oProfile->Get('name')] = $oProfile->GetKey(); 134 } 135 } 136 137 $sCacheKey = $sName; 138 if (isset(self::$m_aCacheProfiles[$sCacheKey])) 139 { 140 return self::$m_aCacheProfiles[$sCacheKey]; 141 } 142 $oNewObj = MetaModel::NewObject("URP_Profiles"); 143 $oNewObj->Set('name', $sName); 144 $oNewObj->Set('description', $sDescription); 145 if ($bReservedName) 146 { 147 $oNewObj->DisableCheckOnReservedNames(); 148 } 149 $iId = $oNewObj->DBInsertNoReload(); 150 self::$m_aCacheProfiles[$sCacheKey] = $iId; 151 return $iId; 152 } 153 154 public static function DoCreateActionGrant($iProfile, $iAction, $sClass, $bPermission = true) 155 { 156 $sAction = self::$m_aActions[$iAction]; 157 158 if (is_null(self::$m_aCacheActionGrants)) 159 { 160 self::$m_aCacheActionGrants = array(); 161 $oFilterAll = new DBObjectSearch('URP_ActionGrant'); 162 $oSet = new DBObjectSet($oFilterAll); 163 while ($oGrant = $oSet->Fetch()) 164 { 165 self::$m_aCacheActionGrants[$oGrant->Get('profileid').'-'.$oGrant->Get('action').'-'.$oGrant->Get('class')] = $oGrant->GetKey(); 166 } 167 } 168 169 $sCacheKey = "$iProfile-$sAction-$sClass"; 170 if (isset(self::$m_aCacheActionGrants[$sCacheKey])) 171 { 172 return self::$m_aCacheActionGrants[$sCacheKey]; 173 } 174 175 $oNewObj = MetaModel::NewObject("URP_ActionGrant"); 176 $oNewObj->Set('profileid', $iProfile); 177 $oNewObj->Set('permission', $bPermission ? 'yes' : 'no'); 178 $oNewObj->Set('class', $sClass); 179 $oNewObj->Set('action', $sAction); 180 $iId = $oNewObj->DBInsertNoReload(); 181 self::$m_aCacheActionGrants[$sCacheKey] = $iId; 182 return $iId; 183 } 184 185 public static function DoCreateStimulusGrant($iProfile, $sStimulusCode, $sClass) 186 { 187 if (is_null(self::$m_aCacheStimulusGrants)) 188 { 189 self::$m_aCacheStimulusGrants = array(); 190 $oFilterAll = new DBObjectSearch('URP_StimulusGrant'); 191 $oSet = new DBObjectSet($oFilterAll); 192 while ($oGrant = $oSet->Fetch()) 193 { 194 self::$m_aCacheStimulusGrants[$oGrant->Get('profileid').'-'.$oGrant->Get('stimulus').'-'.$oGrant->Get('class')] = $oGrant->GetKey(); 195 } 196 } 197 198 $sCacheKey = "$iProfile-$sStimulusCode-$sClass"; 199 if (isset(self::$m_aCacheStimulusGrants[$sCacheKey])) 200 { 201 return self::$m_aCacheStimulusGrants[$sCacheKey]; 202 } 203 $oNewObj = MetaModel::NewObject("URP_StimulusGrant"); 204 $oNewObj->Set('profileid', $iProfile); 205 $oNewObj->Set('permission', 'yes'); 206 $oNewObj->Set('class', $sClass); 207 $oNewObj->Set('stimulus', $sStimulusCode); 208 $iId = $oNewObj->DBInsertNoReload(); 209 self::$m_aCacheStimulusGrants[$sCacheKey] = $iId; 210 return $iId; 211 } 212 213 /* 214 * Create the built-in Administrator profile with its reserved name 215 */ 216 public static function DoCreateAdminProfile() 217 { 218 self::DoCreateProfile(ADMIN_PROFILE_NAME, 'Has the rights on everything (bypassing any control)', true /* reserved name */); 219 } 220 221 /* 222 * Overload the standard behavior to preserve reserved names 223 */ 224 public function DoCheckToWrite() 225 { 226 parent::DoCheckToWrite(); 227 228 if ($this->m_bCheckReservedNames) 229 { 230 $aChanges = $this->ListChanges(); 231 if (array_key_exists('name', $aChanges)) 232 { 233 if ($this->GetOriginal('name') == ADMIN_PROFILE_NAME) 234 { 235 $this->m_aCheckIssues[] = "The name of the Administrator profile must not be changed"; 236 } 237 elseif ($this->Get('name') == ADMIN_PROFILE_NAME) 238 { 239 $this->m_aCheckIssues[] = ADMIN_PROFILE_NAME." is a reserved to the built-in Administrator profile"; 240 } 241 elseif ($this->GetOriginal('name') == PORTAL_PROFILE_NAME) 242 { 243 $this->m_aCheckIssues[] = "The name of the User Portal profile must not be changed"; 244 } 245 elseif ($this->Get('name') == PORTAL_PROFILE_NAME) 246 { 247 $this->m_aCheckIssues[] = PORTAL_PROFILE_NAME." is a reserved to the built-in User Portal profile"; 248 } 249 } 250 } 251 } 252 253 function GetGrantAsHtml($oUserRights, $sClass, $sAction) 254 { 255 $iGrant = $oUserRights->GetProfileActionGrant($this->GetKey(), $sClass, $sAction); 256 if (!is_null($iGrant)) 257 { 258 return '<span style="background-color: #ddffdd;">'.Dict::S('UI:UserManagement:ActionAllowed:Yes').'</span>'; 259 } 260 else 261 { 262 return '<span style="background-color: #ffdddd;">'.Dict::S('UI:UserManagement:ActionAllowed:No').'</span>'; 263 } 264 } 265 266 function DoShowGrantSumary($oPage) 267 { 268 if ($this->GetRawName() == "Administrator") 269 { 270 // Looks dirty, but ok that's THE ONE 271 $oPage->p(Dict::S('UI:UserManagement:AdminProfile+')); 272 return; 273 } 274 275 // Note: for sure, we assume that the instance is derived from UserRightsProfile 276 $oUserRights = UserRights::GetModuleInstance(); 277 278 $aDisplayData = array(); 279 foreach (MetaModel::GetClasses('bizmodel') as $sClass) 280 { 281 // Skip non instantiable classes 282 if (MetaModel::IsAbstract($sClass)) continue; 283 284 $aStimuli = array(); 285 foreach (MetaModel::EnumStimuli($sClass) as $sStimulusCode => $oStimulus) 286 { 287 $oGrant = $oUserRights->GetClassStimulusGrant($this->GetKey(), $sClass, $sStimulusCode); 288 if (is_object($oGrant) && ($oGrant->Get('permission') == 'yes')) 289 { 290 $aStimuli[] = '<span title="'.$sStimulusCode.': '.htmlentities($oStimulus->GetDescription(), ENT_QUOTES, 'UTF-8').'">'.htmlentities($oStimulus->GetLabel(), ENT_QUOTES, 'UTF-8').'</span>'; 291 } 292 } 293 $sStimuli = implode(', ', $aStimuli); 294 295 $aDisplayData[] = array( 296 'class' => MetaModel::GetName($sClass), 297 'read' => $this->GetGrantAsHtml($oUserRights, $sClass, 'Read'), 298 'bulkread' => $this->GetGrantAsHtml($oUserRights, $sClass, 'Bulk Read'), 299 'write' => $this->GetGrantAsHtml($oUserRights, $sClass, 'Modify'), 300 'bulkwrite' => $this->GetGrantAsHtml($oUserRights, $sClass, 'Bulk Modify'), 301 'delete' => $this->GetGrantAsHtml($oUserRights, $sClass, 'Delete'), 302 'bulkdelete' => $this->GetGrantAsHtml($oUserRights, $sClass, 'Bulk Delete'), 303 'stimuli' => $sStimuli, 304 ); 305 } 306 307 $aDisplayConfig = array(); 308 $aDisplayConfig['class'] = array('label' => Dict::S('UI:UserManagement:Class'), 'description' => Dict::S('UI:UserManagement:Class+')); 309 $aDisplayConfig['read'] = array('label' => Dict::S('UI:UserManagement:Action:Read'), 'description' => Dict::S('UI:UserManagement:Action:Read+')); 310 $aDisplayConfig['bulkread'] = array('label' => Dict::S('UI:UserManagement:Action:BulkRead'), 'description' => Dict::S('UI:UserManagement:Action:BulkRead+')); 311 $aDisplayConfig['write'] = array('label' => Dict::S('UI:UserManagement:Action:Modify'), 'description' => Dict::S('UI:UserManagement:Action:Modify+')); 312 $aDisplayConfig['bulkwrite'] = array('label' => Dict::S('UI:UserManagement:Action:BulkModify'), 'description' => Dict::S('UI:UserManagement:Action:BulkModify+')); 313 $aDisplayConfig['delete'] = array('label' => Dict::S('UI:UserManagement:Action:Delete'), 'description' => Dict::S('UI:UserManagement:Action:Delete+')); 314 $aDisplayConfig['bulkdelete'] = array('label' => Dict::S('UI:UserManagement:Action:BulkDelete'), 'description' => Dict::S('UI:UserManagement:Action:BulkDelete+')); 315 $aDisplayConfig['stimuli'] = array('label' => Dict::S('UI:UserManagement:Action:Stimuli'), 'description' => Dict::S('UI:UserManagement:Action:Stimuli+')); 316 $oPage->table($aDisplayConfig, $aDisplayData); 317 } 318 319 function DisplayBareRelations(WebPage $oPage, $bEditMode = false) 320 { 321 parent::DisplayBareRelations($oPage, $bEditMode); 322 if (!$bEditMode) 323 { 324 $oPage->SetCurrentTab(Dict::S('UI:UserManagement:GrantMatrix')); 325 $this->DoShowGrantSumary($oPage); 326 } 327 } 328} 329 330 331 332class URP_UserProfile extends UserRightsBaseClassGUI 333{ 334 public static function Init() 335 { 336 $aParams = array 337 ( 338 "category" => "addon/userrights", 339 "key_type" => "autoincrement", 340 "name_attcode" => "userid", 341 "state_attcode" => "", 342 "reconc_keys" => array(), 343 "db_table" => "priv_urp_userprofile", 344 "db_key_field" => "id", 345 "db_finalclass_field" => "", 346 "display_template" => "", 347 ); 348 MetaModel::Init_Params($aParams); 349 //MetaModel::Init_InheritAttributes(); 350 MetaModel::Init_AddAttribute(new AttributeExternalKey("userid", array("targetclass"=>"User", "jointype"=> "", "allowed_values"=>null, "sql"=>"userid", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); 351 MetaModel::Init_AddAttribute(new AttributeExternalField("userlogin", array("allowed_values"=>null, "extkey_attcode"=> 'userid', "target_attcode"=>"login"))); 352 353 MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid", array("targetclass"=>"URP_Profiles", "jointype"=> "", "allowed_values"=>null, "sql"=>"profileid", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); 354 MetaModel::Init_AddAttribute(new AttributeExternalField("profile", array("allowed_values"=>null, "extkey_attcode"=> 'profileid', "target_attcode"=>"name"))); 355 356 MetaModel::Init_AddAttribute(new AttributeString("reason", array("allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); 357 358 // Display lists 359 MetaModel::Init_SetZListItems('details', array('userid', 'profileid', 'reason')); // Attributes to be displayed for the complete details 360 MetaModel::Init_SetZListItems('list', array('userid', 'profileid', 'reason')); // Attributes to be displayed for a list 361 // Search criteria 362 MetaModel::Init_SetZListItems('standard_search', array('userid', 'profileid')); // Criteria of the std search form 363 MetaModel::Init_SetZListItems('advanced_search', array('userid', 'profileid')); // Criteria of the advanced search form 364 } 365 366 public function GetName() 367 { 368 return Dict::Format('UI:UserManagement:LinkBetween_User_And_Profile', $this->Get('userlogin'), $this->Get('profile')); 369 } 370} 371 372class URP_UserOrg extends UserRightsBaseClassGUI 373{ 374 public static function Init() 375 { 376 $aParams = array 377 ( 378 "category" => "addon/userrights", 379 "key_type" => "autoincrement", 380 "name_attcode" => "userid", 381 "state_attcode" => "", 382 "reconc_keys" => array(), 383 "db_table" => "priv_urp_userorg", 384 "db_key_field" => "id", 385 "db_finalclass_field" => "", 386 "display_template" => "", 387 ); 388 MetaModel::Init_Params($aParams); 389 //MetaModel::Init_InheritAttributes(); 390 MetaModel::Init_AddAttribute(new AttributeExternalKey("userid", array("targetclass"=>"User", "jointype"=> "", "allowed_values"=>null, "sql"=>"userid", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); 391 MetaModel::Init_AddAttribute(new AttributeExternalField("userlogin", array("allowed_values"=>null, "extkey_attcode"=> 'userid', "target_attcode"=>"login"))); 392 393 MetaModel::Init_AddAttribute(new AttributeExternalKey("allowed_org_id", array("targetclass"=>"Organization", "jointype"=> "", "allowed_values"=>null, "sql"=>"allowed_org_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_AUTO, "depends_on"=>array()))); 394 MetaModel::Init_AddAttribute(new AttributeExternalField("allowed_org_name", array("allowed_values"=>null, "extkey_attcode"=> 'allowed_org_id', "target_attcode"=>"name"))); 395 396 MetaModel::Init_AddAttribute(new AttributeString("reason", array("allowed_values"=>null, "sql"=>"reason", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); 397 398 // Display lists 399 MetaModel::Init_SetZListItems('details', array('userid', 'allowed_org_id', 'reason')); // Attributes to be displayed for the complete details 400 MetaModel::Init_SetZListItems('list', array('allowed_org_id', 'reason')); // Attributes to be displayed for a list 401 // Search criteria 402 MetaModel::Init_SetZListItems('standard_search', array('userid', 'allowed_org_id')); // Criteria of the std search form 403 MetaModel::Init_SetZListItems('advanced_search', array('userid', 'allowed_org_id')); // Criteria of the advanced search form 404 } 405 406 public function GetName() 407 { 408 return Dict::Format('UI:UserManagement:LinkBetween_User_And_Org', $this->Get('userlogin'), $this->Get('allowed_org_name')); 409 } 410} 411 412 413class URP_ActionGrant extends UserRightsBaseClass 414{ 415 public static function Init() 416 { 417 $aParams = array 418 ( 419 "category" => "addon/userrights", 420 "key_type" => "autoincrement", 421 "name_attcode" => "profileid", 422 "state_attcode" => "", 423 "reconc_keys" => array(), 424 "db_table" => "priv_urp_grant_actions", 425 "db_key_field" => "id", 426 "db_finalclass_field" => "", 427 "display_template" => "", 428 ); 429 MetaModel::Init_Params($aParams); 430 //MetaModel::Init_InheritAttributes(); 431 432 // Common to all grant classes (could be factorized by class inheritence, but this has to be benchmarked) 433 MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid", array("targetclass"=>"URP_Profiles", "jointype"=> "", "allowed_values"=>null, "sql"=>"profileid", "is_null_allowed"=>false, "on_target_delete"=>DEL_SILENT, "depends_on"=>array()))); 434 MetaModel::Init_AddAttribute(new AttributeExternalField("profile", array("allowed_values"=>null, "extkey_attcode"=> 'profileid', "target_attcode"=>"name"))); 435 MetaModel::Init_AddAttribute(new AttributeClass("class", array("class_category"=>"", "more_values"=>"", "sql"=>"class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); 436 MetaModel::Init_AddAttribute(new AttributeEnum("permission", array("allowed_values"=>new ValueSetEnum('yes,no'), "sql"=>"permission", "default_value"=>"yes", "is_null_allowed"=>false, "depends_on"=>array()))); 437 438 MetaModel::Init_AddAttribute(new AttributeString("action", array("allowed_values"=>null, "sql"=>"action", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); 439 440 // Display lists 441 MetaModel::Init_SetZListItems('details', array('profileid', 'class', 'permission', 'action')); // Attributes to be displayed for the complete details 442 MetaModel::Init_SetZListItems('list', array('class', 'permission', 'action')); // Attributes to be displayed for a list 443 // Search criteria 444 MetaModel::Init_SetZListItems('standard_search', array('profileid', 'class', 'permission', 'action')); // Criteria of the std search form 445 MetaModel::Init_SetZListItems('advanced_search', array('profileid', 'class', 'permission', 'action')); // Criteria of the advanced search form 446 } 447} 448 449 450class URP_StimulusGrant extends UserRightsBaseClass 451{ 452 public static function Init() 453 { 454 $aParams = array 455 ( 456 "category" => "addon/userrights", 457 "key_type" => "autoincrement", 458 "name_attcode" => "profileid", 459 "state_attcode" => "", 460 "reconc_keys" => array(), 461 "db_table" => "priv_urp_grant_stimulus", 462 "db_key_field" => "id", 463 "db_finalclass_field" => "", 464 "display_template" => "", 465 ); 466 MetaModel::Init_Params($aParams); 467 //MetaModel::Init_InheritAttributes(); 468 469 // Common to all grant classes (could be factorized by class inheritence, but this has to be benchmarked) 470 MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid", array("targetclass"=>"URP_Profiles", "jointype"=> "", "allowed_values"=>null, "sql"=>"profileid", "is_null_allowed"=>false, "on_target_delete"=>DEL_SILENT, "depends_on"=>array()))); 471 MetaModel::Init_AddAttribute(new AttributeExternalField("profile", array("allowed_values"=>null, "extkey_attcode"=> 'profileid', "target_attcode"=>"name"))); 472 MetaModel::Init_AddAttribute(new AttributeClass("class", array("class_category"=>"", "more_values"=>"", "sql"=>"class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); 473 MetaModel::Init_AddAttribute(new AttributeEnum("permission", array("allowed_values"=>new ValueSetEnum('yes,no'), "sql"=>"permission", "default_value"=>"yes", "is_null_allowed"=>false, "depends_on"=>array()))); 474 475 MetaModel::Init_AddAttribute(new AttributeString("stimulus", array("allowed_values"=>null, "sql"=>"action", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); 476 477 // Display lists 478 MetaModel::Init_SetZListItems('details', array('profileid', 'class', 'permission', 'stimulus')); // Attributes to be displayed for the complete details 479 MetaModel::Init_SetZListItems('list', array('class', 'permission', 'stimulus')); // Attributes to be displayed for a list 480 // Search criteria 481 MetaModel::Init_SetZListItems('standard_search', array('profileid', 'class', 'permission', 'stimulus')); // Criteria of the std search form 482 MetaModel::Init_SetZListItems('advanced_search', array('profileid', 'class', 'permission', 'stimulus')); // Criteria of the advanced search form 483 } 484} 485 486 487class URP_AttributeGrant extends UserRightsBaseClass 488{ 489 public static function Init() 490 { 491 $aParams = array 492 ( 493 "category" => "addon/userrights", 494 "key_type" => "autoincrement", 495 "name_attcode" => "actiongrantid", 496 "state_attcode" => "", 497 "reconc_keys" => array(), 498 "db_table" => "priv_urp_grant_attributes", 499 "db_key_field" => "id", 500 "db_finalclass_field" => "", 501 "display_template" => "", 502 ); 503 MetaModel::Init_Params($aParams); 504 //MetaModel::Init_InheritAttributes(); 505 506 MetaModel::Init_AddAttribute(new AttributeExternalKey("actiongrantid", array("targetclass"=>"URP_ActionGrant", "jointype"=> "", "allowed_values"=>null, "sql"=>"actiongrantid", "is_null_allowed"=>false, "on_target_delete"=>DEL_SILENT, "depends_on"=>array()))); 507 MetaModel::Init_AddAttribute(new AttributeString("attcode", array("allowed_values"=>null, "sql"=>"attcode", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); 508 509 // Display lists 510 MetaModel::Init_SetZListItems('details', array('actiongrantid', 'attcode')); // Attributes to be displayed for the complete details 511 MetaModel::Init_SetZListItems('list', array('attcode')); // Attributes to be displayed for a list 512 // Search criteria 513 MetaModel::Init_SetZListItems('standard_search', array('actiongrantid', 'attcode')); // Criteria of the std search form 514 MetaModel::Init_SetZListItems('advanced_search', array('actiongrantid', 'attcode')); // Criteria of the advanced search form 515 } 516} 517 518 519 520 521class UserRightsProfile extends UserRightsAddOnAPI 522{ 523 static public $m_aActionCodes = array( 524 UR_ACTION_READ => 'read', 525 UR_ACTION_MODIFY => 'modify', 526 UR_ACTION_DELETE => 'delete', 527 UR_ACTION_BULK_READ => 'bulk read', 528 UR_ACTION_BULK_MODIFY => 'bulk modify', 529 UR_ACTION_BULK_DELETE => 'bulk delete', 530 ); 531 532 // Installation: create the very first user 533 public function CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage = 'EN US') 534 { 535 // Create a change to record the history of the User object 536 $oChange = MetaModel::NewObject("CMDBChange"); 537 $oChange->Set("date", time()); 538 $oChange->Set("userinfo", "Initialization"); 539 $iChangeId = $oChange->DBInsert(); 540 541 $iContactId = 0; 542 // Support drastic data model changes: no organization class (or not writable)! 543 if (MetaModel::IsValidClass('Organization') && !MetaModel::IsAbstract('Organization')) 544 { 545 $oOrg = new Organization(); 546 $oOrg->Set('name', 'My Company/Department'); 547 $oOrg->Set('code', 'SOMECODE'); 548 $iOrgId = $oOrg->DBInsertTrackedNoReload($oChange, true /* skip security */); 549 550 // Support drastic data model changes: no Person class (or not writable)! 551 if (MetaModel::IsValidClass('Person') && !MetaModel::IsAbstract('Person')) 552 { 553 $oContact = new Person(); 554 $oContact->Set('name', 'My last name'); 555 $oContact->Set('first_name', 'My first name'); 556 if (MetaModel::IsValidAttCode('Person', 'org_id')) 557 { 558 $oContact->Set('org_id', $iOrgId); 559 } 560 if (MetaModel::IsValidAttCode('Person', 'phone')) 561 { 562 $oContact->Set('phone', '+00 000 000 000'); 563 } 564 $oContact->Set('email', 'my.email@foo.org'); 565 $iContactId = $oContact->DBInsertTrackedNoReload($oChange, true /* skip security */); 566 } 567 } 568 569 570 $oUser = new UserLocal(); 571 $oUser->Set('login', $sAdminUser); 572 $oUser->Set('password', $sAdminPwd); 573 if (MetaModel::IsValidAttCode('UserLocal', 'contactid') && ($iContactId != 0)) 574 { 575 $oUser->Set('contactid', $iContactId); 576 } 577 $oUser->Set('language', $sLanguage); // Language was chosen during the installation 578 579 // Add this user to the very specific 'admin' profile 580 $oAdminProfile = MetaModel::GetObjectFromOQL("SELECT URP_Profiles WHERE name = :name", array('name' => ADMIN_PROFILE_NAME), true /*all data*/); 581 if (is_object($oAdminProfile)) 582 { 583 $oUserProfile = new URP_UserProfile(); 584 //$oUserProfile->Set('userid', $iUserId); 585 $oUserProfile->Set('profileid', $oAdminProfile->GetKey()); 586 $oUserProfile->Set('reason', 'By definition, the administrator must have the administrator profile'); 587 //$oUserProfile->DBInsertTrackedNoReload($oChange, true /* skip security */); 588 $oSet = DBObjectSet::FromObject($oUserProfile); 589 $oUser->Set('profile_list', $oSet); 590 } 591 $iUserId = $oUser->DBInsertTrackedNoReload($oChange, true /* skip security */); 592 return true; 593 } 594 595 public function Init() 596 { 597 } 598 599 600 protected $m_aAdmins = array(); // id -> bool, true if the user has the well-known admin profile 601 protected $m_aPortalUsers = array(); // id -> bool, true if the user has the well-known portal user profile 602 603 protected $m_aProfiles; // id -> object 604 protected $m_aUserProfiles = array(); // userid,profileid -> object 605 protected $m_aUserOrgs = array(); // userid -> array of orgid 606 607 // Those arrays could be completed on demand (inheriting parent permissions) 608 protected $m_aClassActionGrants = null; // profile, class, action -> actiongrantid (or false if NO, or null/missing if undefined) 609 protected $m_aClassStimulusGrants = array(); // profile, class, stimulus -> permission 610 611 // Built on demand, could be optimized if necessary (doing a query for each attribute that needs to be read) 612 protected $m_aObjectActionGrants = array(); 613 614 /** 615 * Read and cache organizations allowed to the given user 616 * 617 * @param $oUser 618 * @param $sClass (not used here but can be used in overloads) 619 * 620 * @return array 621 * @throws \CoreException 622 * @throws \Exception 623 */ 624 public function GetUserOrgs($oUser, $sClass) 625 { 626 $iUser = $oUser->GetKey(); 627 if (!array_key_exists($iUser, $this->m_aUserOrgs)) 628 { 629 $this->m_aUserOrgs[$iUser] = array(); 630 631 $sHierarchicalKeyCode = MetaModel::IsHierarchicalClass('Organization'); 632 if ($sHierarchicalKeyCode !== false) 633 { 634 $sUserOrgQuery = 'SELECT UserOrg, Org FROM Organization AS Org JOIN Organization AS Root ON Org.'.$sHierarchicalKeyCode.' BELOW Root.id JOIN URP_UserOrg AS UserOrg ON UserOrg.allowed_org_id = Root.id WHERE UserOrg.userid = :userid'; 635 $oUserOrgSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData($sUserOrgQuery), array(), array('userid' => $iUser)); 636 while ($aRow = $oUserOrgSet->FetchAssoc()) 637 { 638 $oOrg = $aRow['Org']; 639 $this->m_aUserOrgs[$iUser][] = $oOrg->GetKey(); 640 } 641 } 642 else 643 { 644 $oSearch = new DBObjectSearch('URP_UserOrg'); 645 $oSearch->AllowAllData(); 646 $oCondition = new BinaryExpression(new FieldExpression('userid'), '=', new VariableExpression('userid')); 647 $oSearch->AddConditionExpression($oCondition); 648 649 $oUserOrgSet = new DBObjectSet($oSearch, array(), array('userid' => $iUser)); 650 while ($oUserOrg = $oUserOrgSet->Fetch()) 651 { 652 $this->m_aUserOrgs[$iUser][] = $oUserOrg->Get('allowed_org_id'); 653 } 654 } 655 } 656 return $this->m_aUserOrgs[$iUser]; 657 } 658 659 /** 660 * Read and cache profiles of the given user 661 */ 662 protected function GetUserProfiles($iUser) 663 { 664 if (!array_key_exists($iUser, $this->m_aUserProfiles)) 665 { 666 $oSearch = new DBObjectSearch('URP_UserProfile'); 667 $oSearch->AllowAllData(); 668 $oCondition = new BinaryExpression(new FieldExpression('userid'), '=', new VariableExpression('userid')); 669 $oSearch->AddConditionExpression($oCondition); 670 671 $this->m_aUserProfiles[$iUser] = array(); 672 $oUserProfileSet = new DBObjectSet($oSearch, array(), array('userid' => $iUser)); 673 while ($oUserProfile = $oUserProfileSet->Fetch()) 674 { 675 $this->m_aUserProfiles[$iUser][$oUserProfile->Get('profileid')] = $oUserProfile; 676 } 677 } 678 return $this->m_aUserProfiles[$iUser]; 679 680 } 681 682 public function ResetCache() 683 { 684 // Loaded by Load cache 685 $this->m_aProfiles = null; 686 $this->m_aUserProfiles = array(); 687 $this->m_aUserOrgs = array(); 688 689 $this->m_aAdmins = array(); 690 $this->m_aPortalUsers = array(); 691 692 // Loaded on demand (time consuming as compared to the others) 693 $this->m_aClassActionGrants = null; 694 $this->m_aClassStimulusGrants = null; 695 696 $this->m_aObjectActionGrants = array(); 697 } 698 699 // Separate load: this cache is much more time consuming while loading 700 // Thus it is loaded iif required 701 // Could be improved by specifying the profile id 702 public function LoadActionGrantCache() 703 { 704 if (!is_null($this->m_aClassActionGrants)) return; 705 706 $oKPI = new ExecutionKPI(); 707 708 $oFilter = DBObjectSearch::FromOQL_AllData("SELECT URP_ActionGrant AS p WHERE p.permission = 'yes'"); 709 $aGrants = $oFilter->ToDataArray(); 710 foreach($aGrants as $aGrant) 711 { 712 $this->m_aClassActionGrants[$aGrant['profileid']][$aGrant['class']][strtolower($aGrant['action'])] = $aGrant['id']; 713 } 714 715 $oKPI->ComputeAndReport('Load of action grants'); 716 } 717 718 public function LoadCache() 719 { 720 if (!is_null($this->m_aProfiles)) return; 721 // Could be loaded in a shared memory (?) 722 723 $oKPI = new ExecutionKPI(); 724 725 if (self::HasSharing()) 726 { 727 SharedObject::InitSharedClassProperties(); 728 } 729 730 $oProfileSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_Profiles")); 731 $this->m_aProfiles = array(); 732 while ($oProfile = $oProfileSet->Fetch()) 733 { 734 $this->m_aProfiles[$oProfile->GetKey()] = $oProfile; 735 } 736 737 $this->m_aClassStimulusGrants = array(); 738 $oStimGrantSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_StimulusGrant")); 739 $this->m_aStimGrants = array(); 740 while ($oStimGrant = $oStimGrantSet->Fetch()) 741 { 742 $this->m_aClassStimulusGrants[$oStimGrant->Get('profileid')][$oStimGrant->Get('class')][$oStimGrant->Get('stimulus')] = $oStimGrant; 743 } 744 745 $oKPI->ComputeAndReport('Load of user management cache (excepted Action Grants)'); 746 747/* 748 echo "<pre>\n"; 749 print_r($this->m_aProfiles); 750 print_r($this->m_aUserProfiles); 751 print_r($this->m_aUserOrgs); 752 print_r($this->m_aClassActionGrants); 753 print_r($this->m_aClassStimulusGrants); 754 echo "</pre>\n"; 755exit; 756*/ 757 758 return true; 759 } 760 761 public function IsAdministrator($oUser) 762 { 763 //$this->LoadCache(); 764 $iUser = $oUser->GetKey(); 765 if (!array_key_exists($iUser, $this->m_aAdmins)) 766 { 767 $bIsAdmin = false; 768 foreach($this->GetUserProfiles($iUser) as $oUserProfile) 769 { 770 if ($oUserProfile->Get('profile') == ADMIN_PROFILE_NAME) 771 { 772 $bIsAdmin = true; 773 break; 774 } 775 } 776 $this->m_aAdmins[$iUser] = $bIsAdmin; 777 } 778 return $this->m_aAdmins[$iUser]; 779 } 780 781 public function IsPortalUser($oUser) 782 { 783 //$this->LoadCache(); 784 $iUser = $oUser->GetKey(); 785 if (!array_key_exists($iUser, $this->m_aPortalUsers)) 786 { 787 $bIsPortalUser = false; 788 foreach($this->GetUserProfiles($iUser) as $oUserProfile) 789 { 790 if ($oUserProfile->Get('profile') == PORTAL_PROFILE_NAME) 791 { 792 $bIsPortalUser = true; 793 break; 794 } 795 } 796 $this->m_aPortalUsers[$iUser] = $bIsPortalUser; 797 } 798 return $this->m_aPortalUsers[$iUser]; 799 } 800 801 public function GetSelectFilter($oUser, $sClass, $aSettings = array()) 802 { 803 $this->LoadCache(); 804 805 $aObjectPermissions = $this->GetUserActionGrant($oUser, $sClass, UR_ACTION_READ); 806 if ($aObjectPermissions['permission'] == UR_ALLOWED_NO) 807 { 808 return false; 809 } 810 811 // Determine how to position the objects of this class 812 // 813 $sAttCode = self::GetOwnerOrganizationAttCode($sClass); 814 if (is_null($sAttCode)) 815 { 816 // No filtering for this object 817 return true; 818 } 819 // Position the user 820 // 821 $aUserOrgs = $this->GetUserOrgs($oUser, $sClass); 822 if (count($aUserOrgs) == 0) 823 { 824 // No org means 'any org' 825 return true; 826 } 827 828 return $this->MakeSelectFilter($sClass, $aUserOrgs, $aSettings, $sAttCode); 829 } 830 831 // This verb has been made public to allow the development of an accurate feedback for the current configuration 832 public function GetProfileActionGrant($iProfile, $sClass, $sAction) 833 { 834 $this->LoadActionGrantCache(); 835 836 // Note: action is forced lowercase to be more flexible (historical bug) 837 $sAction = strtolower($sAction); 838 if (isset($this->m_aClassActionGrants[$iProfile][$sClass][$sAction])) 839 { 840 return $this->m_aClassActionGrants[$iProfile][$sClass][$sAction]; 841 } 842 843 // Recursively look for the grant record in the class hierarchy 844 $sParentClass = MetaModel::GetParentPersistentClass($sClass); 845 if (empty($sParentClass)) 846 { 847 $iGrant = null; 848 } 849 else 850 { 851 // Recursively look for the grant record in the class hierarchy 852 $iGrant = $this->GetProfileActionGrant($iProfile, $sParentClass, $sAction); 853 } 854 855 $this->m_aClassActionGrants[$iProfile][$sClass][$sAction] = $iGrant; 856 return $iGrant; 857 } 858 859 protected function GetUserActionGrant($oUser, $sClass, $iActionCode) 860 { 861 $this->LoadCache(); 862 863 // load and cache permissions for the current user on the given class 864 // 865 $iUser = $oUser->GetKey(); 866 $aTest = @$this->m_aObjectActionGrants[$iUser][$sClass][$iActionCode]; 867 if (is_array($aTest)) return $aTest; 868 869 $sAction = self::$m_aActionCodes[$iActionCode]; 870 871 $iPermission = UR_ALLOWED_NO; 872 $aAttributes = array(); 873 foreach($this->GetUserProfiles($iUser) as $iProfile => $oProfile) 874 { 875 $iGrant = $this->GetProfileActionGrant($iProfile, $sClass, $sAction); 876 if (is_null($iGrant) || !$iGrant) 877 { 878 continue; // loop to the next profile 879 } 880 else 881 { 882 $iPermission = UR_ALLOWED_YES; 883 884 // update the list of attributes with those allowed for this profile 885 // 886 $oSearch = DBObjectSearch::FromOQL_AllData("SELECT URP_AttributeGrant WHERE actiongrantid = :actiongrantid"); 887 $oSet = new DBObjectSet($oSearch, array(), array('actiongrantid' => $iGrant)); 888 $aProfileAttributes = $oSet->GetColumnAsArray('attcode', false); 889 if (count($aProfileAttributes) == 0) 890 { 891 $aAllAttributes = array_keys(MetaModel::ListAttributeDefs($sClass)); 892 $aAttributes = array_merge($aAttributes, $aAllAttributes); 893 } 894 else 895 { 896 $aAttributes = array_merge($aAttributes, $aProfileAttributes); 897 } 898 } 899 } 900 901 $aRes = array( 902 'permission' => $iPermission, 903 'attributes' => $aAttributes, 904 ); 905 $this->m_aObjectActionGrants[$iUser][$sClass][$iActionCode] = $aRes; 906 return $aRes; 907 } 908 909 public function IsActionAllowed($oUser, $sClass, $iActionCode, $oInstanceSet = null) 910 { 911 $this->LoadCache(); 912 913 $aObjectPermissions = $this->GetUserActionGrant($oUser, $sClass, $iActionCode); 914 $iPermission = $aObjectPermissions['permission']; 915 916 // Note: In most cases the object set is ignored because it was interesting to optimize for huge data sets 917 // and acceptable to consider only the root class of the object set 918 919 if ($iPermission != UR_ALLOWED_YES) 920 { 921 // It is already NO for everyone... that's the final word! 922 } 923 elseif ($iActionCode == UR_ACTION_READ) 924 { 925 // We are protected by GetSelectFilter: the object set contains objects allowed or shared for reading 926 } 927 elseif ($iActionCode == UR_ACTION_BULK_READ) 928 { 929 // We are protected by GetSelectFilter: the object set contains objects allowed or shared for reading 930 } 931 elseif ($oInstanceSet) 932 { 933 // We are protected by GetSelectFilter: the object set contains objects allowed or shared for reading 934 // We have to answer NO for objects shared for reading purposes 935 if (self::HasSharing()) 936 { 937 $aClassProps = SharedObject::GetSharedClassProperties($sClass); 938 if ($aClassProps) 939 { 940 // This class is shared, GetSelectFilter may allow some objects for read only 941 // But currently we are checking wether the objects might be written... 942 // Let's exclude the objects based on the relevant criteria 943 944 $sOrgAttCode = self::GetOwnerOrganizationAttCode($sClass); 945 if (!is_null($sOrgAttCode)) 946 { 947 $aUserOrgs = $this->GetUserOrgs($oUser, $sClass); 948 if (!is_null($aUserOrgs) && count($aUserOrgs) > 0) 949 { 950 $iCountNO = 0; 951 $iCountYES = 0; 952 $oInstanceSet->Rewind(); 953 while($oObject = $oInstanceSet->Fetch()) 954 { 955 $iOrg = $oObject->Get($sOrgAttCode); 956 if (in_array($iOrg, $aUserOrgs)) 957 { 958 $iCountYES++; 959 } 960 else 961 { 962 $iCountNO++; 963 } 964 } 965 if ($iCountNO == 0) 966 { 967 $iPermission = UR_ALLOWED_YES; 968 } 969 elseif ($iCountYES == 0) 970 { 971 $iPermission = UR_ALLOWED_NO; 972 } 973 else 974 { 975 $iPermission = UR_ALLOWED_DEPENDS; 976 } 977 } 978 } 979 } 980 } 981 } 982 return $iPermission; 983 } 984 985 public function IsActionAllowedOnAttribute($oUser, $sClass, $sAttCode, $iActionCode, $oInstanceSet = null) 986 { 987 $this->LoadCache(); 988 989 // Note: The object set is ignored because it was interesting to optimize for huge data sets 990 // and acceptable to consider only the root class of the object set 991 $aObjectPermissions = $this->GetUserActionGrant($oUser, $sClass, $iActionCode); 992 $aAttributes = $aObjectPermissions['attributes']; 993 if (in_array($sAttCode, $aAttributes)) 994 { 995 return $aObjectPermissions['permission']; 996 } 997 else 998 { 999 return UR_ALLOWED_NO; 1000 } 1001 } 1002 1003 // This verb has been made public to allow the development of an accurate feedback for the current configuration 1004 public function GetClassStimulusGrant($iProfile, $sClass, $sStimulusCode) 1005 { 1006 $this->LoadCache(); 1007 1008 if (isset($this->m_aClassStimulusGrants[$iProfile][$sClass][$sStimulusCode])) 1009 { 1010 return $this->m_aClassStimulusGrants[$iProfile][$sClass][$sStimulusCode]; 1011 } 1012 else 1013 { 1014 return null; 1015 } 1016 } 1017 1018 public function IsStimulusAllowed($oUser, $sClass, $sStimulusCode, $oInstanceSet = null) 1019 { 1020 $this->LoadCache(); 1021 // Note: this code is VERY close to the code of IsActionAllowed() 1022 $iUser = $oUser->GetKey(); 1023 1024 // Note: The object set is ignored because it was interesting to optimize for huge data sets 1025 // and acceptable to consider only the root class of the object set 1026 $iPermission = UR_ALLOWED_NO; 1027 foreach($this->GetUserProfiles($iUser) as $iProfile => $oProfile) 1028 { 1029 $oGrantRecord = $this->GetClassStimulusGrant($iProfile, $sClass, $sStimulusCode); 1030 if (!is_null($oGrantRecord)) 1031 { 1032 // no need to fetch the record, we've requested the records having permission = 'yes' 1033 $iPermission = UR_ALLOWED_YES; 1034 } 1035 } 1036 return $iPermission; 1037 } 1038 1039 public function FlushPrivileges() 1040 { 1041 $this->ResetCache(); 1042 } 1043 1044 /** 1045 * Find out which attribute is corresponding the the dimension 'owner org' 1046 * returns null if no such attribute has been found (no filtering should occur) 1047 */ 1048 public static function GetOwnerOrganizationAttCode($sClass) 1049 { 1050 $sAttCode = null; 1051 1052 $aCallSpec = array($sClass, 'MapContextParam'); 1053 if (($sClass == 'Organization') || is_subclass_of($sClass, 'Organization')) 1054 { 1055 $sAttCode = 'id'; 1056 } 1057 elseif (is_callable($aCallSpec)) 1058 { 1059 $sAttCode = call_user_func($aCallSpec, 'org_id'); // Returns null when there is no mapping for this parameter 1060 if (!MetaModel::IsValidAttCode($sClass, $sAttCode)) 1061 { 1062 // Skip silently. The data model checker will tell you something about this... 1063 $sAttCode = null; 1064 } 1065 } 1066 elseif(MetaModel::IsValidAttCode($sClass, 'org_id')) 1067 { 1068 $sAttCode = 'org_id'; 1069 } 1070 1071 return $sAttCode; 1072 } 1073 1074 /** 1075 * Determine wether the objects can be shared by the mean of a class SharedObject 1076 **/ 1077 protected static function HasSharing() 1078 { 1079 static $bHasSharing; 1080 if (!isset($bHasSharing)) 1081 { 1082 $bHasSharing = class_exists('SharedObject'); 1083 } 1084 return $bHasSharing; 1085 } 1086} 1087 1088 1089UserRights::SelectModule('UserRightsProfile'); 1090 1091?> 1092