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 50 51class URP_Profiles extends UserRightsBaseClassGUI 52{ 53 public static function Init() 54 { 55 $aParams = array 56 ( 57 "category" => "addon/userrights,grant_by_profile", 58 "key_type" => "autoincrement", 59 "name_attcode" => "name", 60 "state_attcode" => "", 61 "reconc_keys" => array(), 62 "db_table" => "priv_urp_profiles", 63 "db_key_field" => "id", 64 "db_finalclass_field" => "", 65 "display_template" => "", 66 ); 67 MetaModel::Init_Params($aParams); 68 //MetaModel::Init_InheritAttributes(); 69 MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); 70 MetaModel::Init_AddAttribute(new AttributeString("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); 71 72 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()))); 73 74 // Display lists 75 MetaModel::Init_SetZListItems('details', array('name', 'description', 'user_list')); // Attributes to be displayed for the complete details 76 MetaModel::Init_SetZListItems('list', array('description')); // Attributes to be displayed for a list 77 // Search criteria 78 MetaModel::Init_SetZListItems('standard_search', array('name','description')); // Criteria of the std search form 79 MetaModel::Init_SetZListItems('default_search', array ('name','description')); 80 } 81 82 protected static $m_aCacheProfiles = null; 83 84 public static function DoCreateProfile($sName, $sDescription) 85 { 86 if (is_null(self::$m_aCacheProfiles)) 87 { 88 self::$m_aCacheProfiles = array(); 89 $oFilterAll = new DBObjectSearch('URP_Profiles'); 90 $oSet = new DBObjectSet($oFilterAll); 91 while ($oProfile = $oSet->Fetch()) 92 { 93 self::$m_aCacheProfiles[$oProfile->Get('name')] = $oProfile->GetKey(); 94 } 95 } 96 97 $sCacheKey = $sName; 98 if (isset(self::$m_aCacheProfiles[$sCacheKey])) 99 { 100 return self::$m_aCacheProfiles[$sCacheKey]; 101 } 102 $oNewObj = MetaModel::NewObject("URP_Profiles"); 103 $oNewObj->Set('name', $sName); 104 $oNewObj->Set('description', $sDescription); 105 $iId = $oNewObj->DBInsertNoReload(); 106 self::$m_aCacheProfiles[$sCacheKey] = $iId; 107 return $iId; 108 } 109 110 function GetGrantAsHtml($oUserRights, $sClass, $sAction) 111 { 112 $bGrant = $oUserRights->GetProfileActionGrant($this->GetKey(), $sClass, $sAction); 113 if (is_null($bGrant)) 114 { 115 return '<span style="background-color: #ffdddd;">'.Dict::S('UI:UserManagement:ActionAllowed:No').'</span>'; 116 } 117 elseif ($bGrant) 118 { 119 return '<span style="background-color: #ddffdd;">'.Dict::S('UI:UserManagement:ActionAllowed:Yes').'</span>'; 120 } 121 else 122 { 123 return '<span style="background-color: #ffdddd;">'.Dict::S('UI:UserManagement:ActionAllowed:No').'</span>'; 124 } 125 } 126 127 function DoShowGrantSumary($oPage) 128 { 129 if ($this->GetRawName() == "Administrator") 130 { 131 // Looks dirty, but ok that's THE ONE 132 $oPage->p(Dict::S('UI:UserManagement:AdminProfile+')); 133 return; 134 } 135 136 // Note: for sure, we assume that the instance is derived from UserRightsProfile 137 $oUserRights = UserRights::GetModuleInstance(); 138 139 $aDisplayData = array(); 140 foreach (MetaModel::GetClasses('bizmodel,grant_by_profile') as $sClass) 141 { 142 $aStimuli = array(); 143 foreach (MetaModel::EnumStimuli($sClass) as $sStimulusCode => $oStimulus) 144 { 145 $bGrant = $oUserRights->GetClassStimulusGrant($this->GetKey(), $sClass, $sStimulusCode); 146 if ($bGrant === true) 147 { 148 $aStimuli[] = '<span title="'.$sStimulusCode.': '.htmlentities($oStimulus->GetDescription(), ENT_QUOTES, 'UTF-8').'">'.htmlentities($oStimulus->GetLabel(), ENT_QUOTES, 'UTF-8').'</span>'; 149 } 150 } 151 $sStimuli = implode(', ', $aStimuli); 152 153 $aDisplayData[] = array( 154 'class' => MetaModel::GetName($sClass), 155 'read' => $this->GetGrantAsHtml($oUserRights, $sClass, 'r'), 156 'bulkread' => $this->GetGrantAsHtml($oUserRights, $sClass, 'br'), 157 'write' => $this->GetGrantAsHtml($oUserRights, $sClass, 'w'), 158 'bulkwrite' => $this->GetGrantAsHtml($oUserRights, $sClass, 'bw'), 159 'delete' => $this->GetGrantAsHtml($oUserRights, $sClass, 'd'), 160 'bulkdelete' => $this->GetGrantAsHtml($oUserRights, $sClass, 'bd'), 161 'stimuli' => $sStimuli, 162 ); 163 } 164 165 $aDisplayConfig = array(); 166 $aDisplayConfig['class'] = array('label' => Dict::S('UI:UserManagement:Class'), 'description' => Dict::S('UI:UserManagement:Class+')); 167 $aDisplayConfig['read'] = array('label' => Dict::S('UI:UserManagement:Action:Read'), 'description' => Dict::S('UI:UserManagement:Action:Read+')); 168 $aDisplayConfig['bulkread'] = array('label' => Dict::S('UI:UserManagement:Action:BulkRead'), 'description' => Dict::S('UI:UserManagement:Action:BulkRead+')); 169 $aDisplayConfig['write'] = array('label' => Dict::S('UI:UserManagement:Action:Modify'), 'description' => Dict::S('UI:UserManagement:Action:Modify+')); 170 $aDisplayConfig['bulkwrite'] = array('label' => Dict::S('UI:UserManagement:Action:BulkModify'), 'description' => Dict::S('UI:UserManagement:Action:BulkModify+')); 171 $aDisplayConfig['delete'] = array('label' => Dict::S('UI:UserManagement:Action:Delete'), 'description' => Dict::S('UI:UserManagement:Action:Delete+')); 172 $aDisplayConfig['bulkdelete'] = array('label' => Dict::S('UI:UserManagement:Action:BulkDelete'), 'description' => Dict::S('UI:UserManagement:Action:BulkDelete+')); 173 $aDisplayConfig['stimuli'] = array('label' => Dict::S('UI:UserManagement:Action:Stimuli'), 'description' => Dict::S('UI:UserManagement:Action:Stimuli+')); 174 $oPage->table($aDisplayConfig, $aDisplayData); 175 } 176 177 function DisplayBareRelations(WebPage $oPage, $bEditMode = false) 178 { 179 parent::DisplayBareRelations($oPage, $bEditMode); 180 if (!$bEditMode) 181 { 182 $oPage->SetCurrentTab(Dict::S('UI:UserManagement:GrantMatrix')); 183 $this->DoShowGrantSumary($oPage); 184 } 185 } 186 187 public static function GetReadOnlyAttributes() 188 { 189 return array('name', 'description'); 190 } 191 192 193 // returns an array of id => array of column => php value(so-called "real value") 194 public static function GetPredefinedObjects() 195 { 196 return ProfilesConfig::GetProfilesValues(); 197 } 198 199 // Before deleting a profile, 200 // preserve DB integrity by deleting links to users 201 protected function OnDelete() 202 { 203 // Don't remove admin profile 204 if ($this->Get('name') === ADMIN_PROFILE_NAME) 205 { 206 throw new SecurityException(Dict::Format('UI:Login:Error:AccessAdmin')); 207 } 208 209 // Note: this may break the rule that says: "a user must have at least ONE profile" ! 210 $oLnkSet = $this->Get('user_list'); 211 while($oLnk = $oLnkSet->Fetch()) 212 { 213 $oLnk->DBDelete(); 214 } 215 } 216 217 /** 218 * Returns the set of flags (OPT_ATT_HIDDEN, OPT_ATT_READONLY, OPT_ATT_MANDATORY...) 219 * for the given attribute in the current state of the object 220 * @param $sAttCode string $sAttCode The code of the attribute 221 * @param $aReasons array To store the reasons why the attribute is read-only (info about the synchro replicas) 222 * @param $sTargetState string The target state in which to evalutate the flags, if empty the current state will be used 223 * @return integer Flags: the binary combination of the flags applicable to this attribute 224 */ 225 public function GetAttributeFlags($sAttCode, &$aReasons = array(), $sTargetState = '') 226 { 227 $iFlags = parent::GetAttributeFlags($sAttCode, $aReasons, $sTargetState); 228 if (MetaModel::GetConfig()->Get('demo_mode')) 229 { 230 $aReasons[] = 'Sorry, profiles are read-only in the demonstration mode!'; 231 $iFlags |= OPT_ATT_READONLY; 232 } 233 return $iFlags; 234 } 235} 236 237 238 239class URP_UserProfile extends UserRightsBaseClassGUI 240{ 241 public static function Init() 242 { 243 $aParams = array 244 ( 245 "category" => "addon/userrights,grant_by_profile", 246 "key_type" => "autoincrement", 247 "name_attcode" => "userid", 248 "state_attcode" => "", 249 "reconc_keys" => array(), 250 "db_table" => "priv_urp_userprofile", 251 "db_key_field" => "id", 252 "db_finalclass_field" => "", 253 "display_template" => "", 254 ); 255 MetaModel::Init_Params($aParams); 256 //MetaModel::Init_InheritAttributes(); 257 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()))); 258 MetaModel::Init_AddAttribute(new AttributeExternalField("userlogin", array("allowed_values"=>null, "extkey_attcode"=> 'userid', "target_attcode"=>"login"))); 259 260 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()))); 261 MetaModel::Init_AddAttribute(new AttributeExternalField("profile", array("allowed_values"=>null, "extkey_attcode"=> 'profileid', "target_attcode"=>"name"))); 262 263 MetaModel::Init_AddAttribute(new AttributeString("reason", array("allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); 264 265 // Display lists 266 MetaModel::Init_SetZListItems('details', array('userid', 'profileid', 'reason')); // Attributes to be displayed for the complete details 267 MetaModel::Init_SetZListItems('list', array('userid', 'profileid', 'reason')); // Attributes to be displayed for a list 268 // Search criteria 269 MetaModel::Init_SetZListItems('standard_search', array('userid', 'profileid')); // Criteria of the std search form 270 MetaModel::Init_SetZListItems('advanced_search', array('userid', 'profileid')); // Criteria of the advanced search form 271 } 272 273 public function GetName() 274 { 275 return Dict::Format('UI:UserManagement:LinkBetween_User_And_Profile', $this->Get('userlogin'), $this->Get('profile')); 276 } 277 278 public function CheckToDelete(&$oDeletionPlan) 279 { 280 if (MetaModel::GetConfig()->Get('demo_mode')) 281 { 282 // Users deletion is NOT allowed in demo mode 283 $oDeletionPlan->AddToDelete($this, null); 284 $oDeletionPlan->SetDeletionIssues($this, array('deletion not allowed in demo mode.'), true); 285 $oDeletionPlan->ComputeResults(); 286 return false; 287 } 288 return parent::CheckToDelete($oDeletionPlan); 289 } 290 291 protected function OnInsert() 292 { 293 $this->CheckIfProfileIsAllowed(UR_ACTION_CREATE); 294 } 295 296 protected function OnUpdate() 297 { 298 $this->CheckIfProfileIsAllowed(UR_ACTION_MODIFY); 299 } 300 301 protected function OnDelete() 302 { 303 $this->CheckIfProfileIsAllowed(UR_ACTION_DELETE); 304 } 305 306 /** 307 * @param $iActionCode 308 * 309 * @throws \ArchivedObjectException 310 * @throws \CoreException 311 * @throws \SecurityException 312 */ 313 protected function CheckIfProfileIsAllowed($iActionCode) 314 { 315 // When initializing or admin, we need to let everything pass trough 316 if (!UserRights::IsLoggedIn() || UserRights::IsAdministrator()) { return; } 317 318 // Only administrators can manage administrators 319 $iOrigUserId = $this->GetOriginal('userid'); 320 if (!empty($iOrigUserId)) 321 { 322 $oUser = MetaModel::GetObject('User', $iOrigUserId, true, true); 323 if (UserRights::IsAdministrator($oUser) && !UserRights::IsAdministrator()) 324 { 325 throw new SecurityException(Dict::Format('UI:Login:Error:AccessRestricted')); 326 } 327 } 328 $oUser = MetaModel::GetObject('User', $this->Get('userid'), true, true); 329 if (UserRights::IsAdministrator($oUser) && !UserRights::IsAdministrator()) 330 { 331 throw new SecurityException(Dict::Format('UI:Login:Error:AccessRestricted')); 332 } 333 if (!UserRights::IsActionAllowed(get_class($this), $iActionCode, DBObjectSet::FromObject($this))) 334 { 335 throw new SecurityException(Dict::Format('UI:Error:ObjectCannotBeUpdated')); 336 } 337 if (!UserRights::IsAdministrator() && ($this->Get('profile') === ADMIN_PROFILE_NAME)) 338 { 339 throw new SecurityException(Dict::Format('UI:Login:Error:AccessAdmin')); 340 } 341 } 342 343} 344 345class URP_UserOrg extends UserRightsBaseClassGUI 346{ 347 public static function Init() 348 { 349 $aParams = array 350 ( 351 "category" => "addon/userrights,grant_by_profile", 352 "key_type" => "autoincrement", 353 "name_attcode" => "userid", 354 "state_attcode" => "", 355 "reconc_keys" => array(), 356 "db_table" => "priv_urp_userorg", 357 "db_key_field" => "id", 358 "db_finalclass_field" => "", 359 "display_template" => "", 360 ); 361 MetaModel::Init_Params($aParams); 362 //MetaModel::Init_InheritAttributes(); 363 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()))); 364 MetaModel::Init_AddAttribute(new AttributeExternalField("userlogin", array("allowed_values"=>null, "extkey_attcode"=> 'userid', "target_attcode"=>"login"))); 365 366 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()))); 367 MetaModel::Init_AddAttribute(new AttributeExternalField("allowed_org_name", array("allowed_values"=>null, "extkey_attcode"=> 'allowed_org_id', "target_attcode"=>"name"))); 368 369 MetaModel::Init_AddAttribute(new AttributeString("reason", array("allowed_values"=>null, "sql"=>"reason", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); 370 371 // Display lists 372 MetaModel::Init_SetZListItems('details', array('userid', 'allowed_org_id', 'reason')); // Attributes to be displayed for the complete details 373 MetaModel::Init_SetZListItems('list', array('allowed_org_id', 'reason')); // Attributes to be displayed for a list 374 // Search criteria 375 MetaModel::Init_SetZListItems('standard_search', array('userid', 'allowed_org_id')); // Criteria of the std search form 376 MetaModel::Init_SetZListItems('advanced_search', array('userid', 'allowed_org_id')); // Criteria of the advanced search form 377 } 378 379 public function GetName() 380 { 381 return Dict::Format('UI:UserManagement:LinkBetween_User_And_Org', $this->Get('userlogin'), $this->Get('allowed_org_name')); 382 } 383 384 385 protected function OnInsert() 386 { 387 $this->CheckIfOrgIsAllowed(); 388 } 389 390 protected function OnUpdate() 391 { 392 $this->CheckIfOrgIsAllowed(); 393 } 394 395 protected function OnDelete() 396 { 397 $this->CheckIfOrgIsAllowed(); 398 } 399 400 /** 401 * @throws \CoreException 402 */ 403 protected function CheckIfOrgIsAllowed() 404 { 405 if (!UserRights::IsLoggedIn() || UserRights::IsAdministrator()) { return; } 406 407 $oUser = UserRights::GetUserObject(); 408 $oAddon = UserRights::GetModuleInstance(); 409 $aOrgs = $oAddon->GetUserOrgs($oUser, ''); 410 if (count($aOrgs) > 0) 411 { 412 $iOrigOrgId = $this->GetOriginal('allowed_org_id'); 413 if ((!empty($iOrigOrgId) && !in_array($iOrigOrgId, $aOrgs)) || !in_array($this->Get('allowed_org_id'), $aOrgs)) 414 { 415 throw new SecurityException(Dict::Format('Class:User/Error:OrganizationNotAllowed')); 416 } 417 } 418 } 419} 420 421 422 423 424class UserRightsProfile extends UserRightsAddOnAPI 425{ 426 static public $m_aActionCodes = array( 427 UR_ACTION_READ => 'r', 428 UR_ACTION_MODIFY => 'w', 429 UR_ACTION_DELETE => 'd', 430 UR_ACTION_BULK_READ => 'br', 431 UR_ACTION_BULK_MODIFY => 'bw', 432 UR_ACTION_BULK_DELETE => 'bd', 433 ); 434 435 // Installation: create the very first user 436 public function CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage = 'EN US') 437 { 438 CMDBObject::SetTrackInfo('Initialization'); 439 440 $oChange = CMDBObject::GetCurrentChange(); 441 442 $iContactId = 0; 443 // Support drastic data model changes: no organization class (or not writable)! 444 if (MetaModel::IsValidClass('Organization') && !MetaModel::IsAbstract('Organization')) 445 { 446 $oOrg = new Organization(); 447 $oOrg->Set('name', 'My Company/Department'); 448 $oOrg->Set('code', 'SOMECODE'); 449 $iOrgId = $oOrg->DBInsertTrackedNoReload($oChange, true /* skip security */); 450 451 // Support drastic data model changes: no Person class (or not writable)! 452 if (MetaModel::IsValidClass('Person') && !MetaModel::IsAbstract('Person')) 453 { 454 $oContact = new Person(); 455 $oContact->Set('name', 'My last name'); 456 $oContact->Set('first_name', 'My first name'); 457 if (MetaModel::IsValidAttCode('Person', 'org_id')) 458 { 459 $oContact->Set('org_id', $iOrgId); 460 } 461 if (MetaModel::IsValidAttCode('Person', 'phone')) 462 { 463 $oContact->Set('phone', '+00 000 000 000'); 464 } 465 $oContact->Set('email', 'my.email@foo.org'); 466 $iContactId = $oContact->DBInsertTrackedNoReload($oChange, true /* skip security */); 467 } 468 } 469 470 471 $oUser = new UserLocal(); 472 $oUser->Set('login', $sAdminUser); 473 $oUser->Set('password', $sAdminPwd); 474 if (MetaModel::IsValidAttCode('UserLocal', 'contactid') && ($iContactId != 0)) 475 { 476 $oUser->Set('contactid', $iContactId); 477 } 478 $oUser->Set('language', $sLanguage); // Language was chosen during the installation 479 480 // Add this user to the very specific 'admin' profile 481 $oAdminProfile = MetaModel::GetObjectFromOQL("SELECT URP_Profiles WHERE name = :name", array('name' => ADMIN_PROFILE_NAME), true /*all data*/); 482 if (is_object($oAdminProfile)) 483 { 484 $oUserProfile = new URP_UserProfile(); 485 //$oUserProfile->Set('userid', $iUserId); 486 $oUserProfile->Set('profileid', $oAdminProfile->GetKey()); 487 $oUserProfile->Set('reason', 'By definition, the administrator must have the administrator profile'); 488 //$oUserProfile->DBInsertTrackedNoReload($oChange, true /* skip security */); 489 $oSet = DBObjectSet::FromObject($oUserProfile); 490 $oUser->Set('profile_list', $oSet); 491 } 492 $iUserId = $oUser->DBInsertTrackedNoReload($oChange, true /* skip security */); 493 return true; 494 } 495 496 public function Init() 497 { 498 } 499 500 protected $m_aUserOrgs = array(); // userid -> array of orgid 501 502 // Built on demand, could be optimized if necessary (doing a query for each attribute that needs to be read) 503 protected $m_aObjectActionGrants = array(); 504 505 /** 506 * Read and cache organizations allowed to the given user 507 * 508 * @param $oUser 509 * @param $sClass (not used here but can be used in overloads) 510 * 511 * @return array 512 * @throws \CoreException 513 * @throws \Exception 514 */ 515 public function GetUserOrgs($oUser, $sClass) 516 { 517 $iUser = $oUser->GetKey(); 518 if (!array_key_exists($iUser, $this->m_aUserOrgs)) 519 { 520 $this->m_aUserOrgs[$iUser] = array(); 521 522 $sHierarchicalKeyCode = MetaModel::IsHierarchicalClass('Organization'); 523 if ($sHierarchicalKeyCode !== false) 524 { 525 $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'; 526 $oUserOrgSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData($sUserOrgQuery), array(), array('userid' => $iUser)); 527 while ($aRow = $oUserOrgSet->FetchAssoc()) 528 { 529 $oOrg = $aRow['Org']; 530 $this->m_aUserOrgs[$iUser][] = $oOrg->GetKey(); 531 } 532 } 533 else 534 { 535 $oSearch = new DBObjectSearch('URP_UserOrg'); 536 $oSearch->AllowAllData(); 537 $oCondition = new BinaryExpression(new FieldExpression('userid'), '=', new VariableExpression('userid')); 538 $oSearch->AddConditionExpression($oCondition); 539 540 $oUserOrgSet = new DBObjectSet($oSearch, array(), array('userid' => $iUser)); 541 while ($oUserOrg = $oUserOrgSet->Fetch()) 542 { 543 $this->m_aUserOrgs[$iUser][] = $oUserOrg->Get('allowed_org_id'); 544 } 545 } 546 } 547 return $this->m_aUserOrgs[$iUser]; 548 } 549 550 public function ResetCache() 551 { 552 // Loaded by Load cache 553 $this->m_aUserOrgs = array(); 554 555 // Cache 556 $this->m_aObjectActionGrants = array(); 557 } 558 559 public function LoadCache() 560 { 561 static $bSharedObjectInitialized = false; 562 if (!$bSharedObjectInitialized) 563 { 564 $bSharedObjectInitialized = true; 565 if (self::HasSharing()) 566 { 567 SharedObject::InitSharedClassProperties(); 568 } 569 } 570 return true; 571 } 572 573 /** 574 * @param $oUser User 575 * @return array 576 */ 577 public function IsAdministrator($oUser) 578 { 579 // UserRights caches the list for us 580 return UserRights::HasProfile(ADMIN_PROFILE_NAME, $oUser); 581 } 582 583 /** 584 * @param $oUser User 585 * @return array 586 */ 587 public function IsPortalUser($oUser) 588 { 589 // UserRights caches the list for us 590 return UserRights::HasProfile(PORTAL_PROFILE_NAME, $oUser); 591 } 592 /** 593 * @param $oUser User 594 * @return bool 595 */ 596 public function ListProfiles($oUser) 597 { 598 $aRet = array(); 599 $oSearch = new DBObjectSearch('URP_UserProfile'); 600 $oSearch->AllowAllData(); 601 $oSearch->NoContextParameters(); 602 $oSearch->Addcondition('userid', $oUser->GetKey(), '='); 603 $oProfiles = new DBObjectSet($oSearch); 604 while ($oUserProfile = $oProfiles->Fetch()) 605 { 606 $aRet[$oUserProfile->Get('profileid')] = $oUserProfile->Get('profileid_friendlyname'); 607 } 608 return $aRet; 609 } 610 611 public function GetSelectFilter($oUser, $sClass, $aSettings = array()) 612 { 613 $this->LoadCache(); 614 615 $aObjectPermissions = $this->GetUserActionGrant($oUser, $sClass, UR_ACTION_READ); 616 if ($aObjectPermissions['permission'] == UR_ALLOWED_NO) 617 { 618 return false; 619 } 620 621 // Determine how to position the objects of this class 622 // 623 $sAttCode = self::GetOwnerOrganizationAttCode($sClass); 624 if (is_null($sAttCode)) 625 { 626 // No filtering for this object 627 return true; 628 } 629 // Position the user 630 // 631 $aUserOrgs = $this->GetUserOrgs($oUser, $sClass); 632 if (count($aUserOrgs) == 0) 633 { 634 // No org means 'any org' 635 return true; 636 } 637 638 return $this->MakeSelectFilter($sClass, $aUserOrgs, $aSettings, $sAttCode); 639 } 640 641 642 // This verb has been made public to allow the development of an accurate feedback for the current configuration 643 public function GetProfileActionGrant($iProfile, $sClass, $sAction) 644 { 645 // Note: action is forced lowercase to be more flexible (historical bug) 646 $sAction = strtolower($sAction); 647 648 return ProfilesConfig::GetProfileActionGrant($iProfile, $sClass, $sAction); 649 } 650 651 protected function GetUserActionGrant($oUser, $sClass, $iActionCode) 652 { 653 $this->LoadCache(); 654 655 // load and cache permissions for the current user on the given class 656 // 657 $iUser = $oUser->GetKey(); 658 $aTest = @$this->m_aObjectActionGrants[$iUser][$sClass][$iActionCode]; 659 if (is_array($aTest)) return $aTest; 660 661 $sAction = self::$m_aActionCodes[$iActionCode]; 662 663 $bStatus = null; 664 // Call the API of UserRights because it caches the list for us 665 foreach(UserRights::ListProfiles($oUser) as $iProfile => $oProfile) 666 { 667 $bGrant = $this->GetProfileActionGrant($iProfile, $sClass, $sAction); 668 if (!is_null($bGrant)) 669 { 670 if ($bGrant) 671 { 672 if (is_null($bStatus)) 673 { 674 $bStatus = true; 675 } 676 } 677 else 678 { 679 $bStatus = false; 680 } 681 } 682 } 683 684 $iPermission = $bStatus ? UR_ALLOWED_YES : UR_ALLOWED_NO; 685 686 $aRes = array( 687 'permission' => $iPermission, 688 ); 689 $this->m_aObjectActionGrants[$iUser][$sClass][$iActionCode] = $aRes; 690 return $aRes; 691 } 692 693 public function IsActionAllowed($oUser, $sClass, $iActionCode, $oInstanceSet = null) 694 { 695 $this->LoadCache(); 696 697 $aObjectPermissions = $this->GetUserActionGrant($oUser, $sClass, $iActionCode); 698 $iPermission = $aObjectPermissions['permission']; 699 700 // Note: In most cases the object set is ignored because it was interesting to optimize for huge data sets 701 // and acceptable to consider only the root class of the object set 702 703 if ($iPermission != UR_ALLOWED_YES) 704 { 705 // It is already NO for everyone... that's the final word! 706 } 707 elseif ($iActionCode == UR_ACTION_READ) 708 { 709 // We are protected by GetSelectFilter: the object set contains objects allowed or shared for reading 710 } 711 elseif ($iActionCode == UR_ACTION_BULK_READ) 712 { 713 // We are protected by GetSelectFilter: the object set contains objects allowed or shared for reading 714 } 715 elseif ($oInstanceSet) 716 { 717 // We are protected by GetSelectFilter: the object set contains objects allowed or shared for reading 718 // We have to answer NO for objects shared for reading purposes 719 if (self::HasSharing()) 720 { 721 $aClassProps = SharedObject::GetSharedClassProperties($sClass); 722 if ($aClassProps) 723 { 724 // This class is shared, GetSelectFilter may allow some objects for read only 725 // But currently we are checking wether the objects might be written... 726 // Let's exclude the objects based on the relevant criteria 727 728 $sOrgAttCode = self::GetOwnerOrganizationAttCode($sClass); 729 if (!is_null($sOrgAttCode)) 730 { 731 $aUserOrgs = $this->GetUserOrgs($oUser, $sClass); 732 if (!is_null($aUserOrgs) && count($aUserOrgs) > 0) 733 { 734 $iCountNO = 0; 735 $iCountYES = 0; 736 $oInstanceSet->Rewind(); 737 while($oObject = $oInstanceSet->Fetch()) 738 { 739 $iOrg = $oObject->Get($sOrgAttCode); 740 if (in_array($iOrg, $aUserOrgs)) 741 { 742 $iCountYES++; 743 } 744 else 745 { 746 $iCountNO++; 747 } 748 } 749 if ($iCountNO == 0) 750 { 751 $iPermission = UR_ALLOWED_YES; 752 } 753 elseif ($iCountYES == 0) 754 { 755 $iPermission = UR_ALLOWED_NO; 756 } 757 else 758 { 759 $iPermission = UR_ALLOWED_DEPENDS; 760 } 761 } 762 } 763 } 764 } 765 } 766 return $iPermission; 767 } 768 769 public function IsActionAllowedOnAttribute($oUser, $sClass, $sAttCode, $iActionCode, $oInstanceSet = null) 770 { 771 $this->LoadCache(); 772 773 // Note: The object set is ignored because it was interesting to optimize for huge data sets 774 // and acceptable to consider only the root class of the object set 775 $aObjectPermissions = $this->GetUserActionGrant($oUser, $sClass, $iActionCode); 776 return $aObjectPermissions['permission']; 777 } 778 779 // This verb has been made public to allow the development of an accurate feedback for the current configuration 780 public function GetClassStimulusGrant($iProfile, $sClass, $sStimulusCode) 781 { 782 return ProfilesConfig::GetProfileStimulusGrant($iProfile, $sClass, $sStimulusCode); 783 } 784 785 public function IsStimulusAllowed($oUser, $sClass, $sStimulusCode, $oInstanceSet = null) 786 { 787 $this->LoadCache(); 788 // Note: this code is VERY close to the code of IsActionAllowed() 789 $iUser = $oUser->GetKey(); 790 791 // Note: The object set is ignored because it was interesting to optimize for huge data sets 792 // and acceptable to consider only the root class of the object set 793 $bStatus = null; 794 // Call the API of UserRights because it caches the list for us 795 foreach(UserRights::ListProfiles($oUser) as $iProfile => $oProfile) 796 { 797 $bGrant = $this->GetClassStimulusGrant($iProfile, $sClass, $sStimulusCode); 798 if (!is_null($bGrant)) 799 { 800 if ($bGrant) 801 { 802 if (is_null($bStatus)) 803 { 804 $bStatus = true; 805 } 806 } 807 else 808 { 809 $bStatus = false; 810 } 811 } 812 } 813 814 $iPermission = $bStatus ? UR_ALLOWED_YES : UR_ALLOWED_NO; 815 return $iPermission; 816 } 817 818 public function FlushPrivileges() 819 { 820 $this->ResetCache(); 821 } 822 823 /** 824 * Find out which attribute is corresponding the the dimension 'owner org' 825 * returns null if no such attribute has been found (no filtering should occur) 826 */ 827 public static function GetOwnerOrganizationAttCode($sClass) 828 { 829 $sAttCode = null; 830 831 $aCallSpec = array($sClass, 'MapContextParam'); 832 if (($sClass == 'Organization') || is_subclass_of($sClass, 'Organization')) 833 { 834 $sAttCode = 'id'; 835 } 836 elseif (is_callable($aCallSpec)) 837 { 838 $sAttCode = call_user_func($aCallSpec, 'org_id'); // Returns null when there is no mapping for this parameter 839 if (!MetaModel::IsValidAttCode($sClass, $sAttCode)) 840 { 841 // Skip silently. The data model checker will tell you something about this... 842 $sAttCode = null; 843 } 844 } 845 elseif(MetaModel::IsValidAttCode($sClass, 'org_id')) 846 { 847 $sAttCode = 'org_id'; 848 } 849 850 return $sAttCode; 851 } 852 853 /** 854 * Determine wether the objects can be shared by the mean of a class SharedObject 855 **/ 856 protected static function HasSharing() 857 { 858 static $bHasSharing; 859 if (!isset($bHasSharing)) 860 { 861 $bHasSharing = class_exists('SharedObject'); 862 } 863 return $bHasSharing; 864 } 865} 866 867 868UserRights::SelectModule('UserRightsProfile'); 869 870?> 871