diff --git a/addons/userrights/userrightsprofile.db.class.inc.php b/addons/userrights/userrightsprofile.db.class.inc.php new file mode 100644 index 000000000..edd8693e6 --- /dev/null +++ b/addons/userrights/userrightsprofile.db.class.inc.php @@ -0,0 +1,1151 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +define('ADMIN_PROFILE_NAME', 'Administrator'); +define('PORTAL_PROFILE_NAME', 'Portal user'); + +class UserRightsBaseClassGUI extends cmdbAbstractObject +{ + // Whenever something changes, reload the privileges + + protected function AfterInsert() + { + UserRights::FlushPrivileges(); + } + + protected function AfterUpdate() + { + UserRights::FlushPrivileges(); + } + + protected function AfterDelete() + { + UserRights::FlushPrivileges(); + } +} + +class UserRightsBaseClass extends DBObject +{ + // Whenever something changes, reload the privileges + + protected function AfterInsert() + { + UserRights::FlushPrivileges(); + } + + protected function AfterUpdate() + { + UserRights::FlushPrivileges(); + } + + protected function AfterDelete() + { + UserRights::FlushPrivileges(); + } +} + + + + +class URP_Profiles extends UserRightsBaseClassGUI +{ + public static function Init() + { + $aParams = array + ( + "category" => "addon/userrights", + "key_type" => "autoincrement", + "name_attcode" => "name", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_urp_profiles", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + + 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()))); + + // Display lists + MetaModel::Init_SetZListItems('details', array('name', 'description', 'user_list')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('description')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form + } + + protected $m_bCheckReservedNames = true; + protected function DisableCheckOnReservedNames() + { + $this->m_bCheckReservedNames = false; + } + + + protected static $m_aActions = array( + UR_ACTION_READ => 'Read', + UR_ACTION_MODIFY => 'Modify', + UR_ACTION_DELETE => 'Delete', + UR_ACTION_BULK_READ => 'Bulk Read', + UR_ACTION_BULK_MODIFY => 'Bulk Modify', + UR_ACTION_BULK_DELETE => 'Bulk Delete', + ); + + protected static $m_aCacheActionGrants = null; + protected static $m_aCacheStimulusGrants = null; + protected static $m_aCacheProfiles = null; + + public static function DoCreateProfile($sName, $sDescription, $bReservedName = false) + { + if (is_null(self::$m_aCacheProfiles)) + { + self::$m_aCacheProfiles = array(); + $oFilterAll = new DBObjectSearch('URP_Profiles'); + $oSet = new DBObjectSet($oFilterAll); + while ($oProfile = $oSet->Fetch()) + { + self::$m_aCacheProfiles[$oProfile->Get('name')] = $oProfile->GetKey(); + } + } + + $sCacheKey = $sName; + if (isset(self::$m_aCacheProfiles[$sCacheKey])) + { + return self::$m_aCacheProfiles[$sCacheKey]; + } + $oNewObj = MetaModel::NewObject("URP_Profiles"); + $oNewObj->Set('name', $sName); + $oNewObj->Set('description', $sDescription); + if ($bReservedName) + { + $oNewObj->DisableCheckOnReservedNames(); + } + $iId = $oNewObj->DBInsertNoReload(); + self::$m_aCacheProfiles[$sCacheKey] = $iId; + return $iId; + } + + public static function DoCreateActionGrant($iProfile, $iAction, $sClass, $bPermission = true) + { + $sAction = self::$m_aActions[$iAction]; + + if (is_null(self::$m_aCacheActionGrants)) + { + self::$m_aCacheActionGrants = array(); + $oFilterAll = new DBObjectSearch('URP_ActionGrant'); + $oSet = new DBObjectSet($oFilterAll); + while ($oGrant = $oSet->Fetch()) + { + self::$m_aCacheActionGrants[$oGrant->Get('profileid').'-'.$oGrant->Get('action').'-'.$oGrant->Get('class')] = $oGrant->GetKey(); + } + } + + $sCacheKey = "$iProfile-$sAction-$sClass"; + if (isset(self::$m_aCacheActionGrants[$sCacheKey])) + { + return self::$m_aCacheActionGrants[$sCacheKey]; + } + + $oNewObj = MetaModel::NewObject("URP_ActionGrant"); + $oNewObj->Set('profileid', $iProfile); + $oNewObj->Set('permission', $bPermission ? 'yes' : 'no'); + $oNewObj->Set('class', $sClass); + $oNewObj->Set('action', $sAction); + $iId = $oNewObj->DBInsertNoReload(); + self::$m_aCacheActionGrants[$sCacheKey] = $iId; + return $iId; + } + + public static function DoCreateStimulusGrant($iProfile, $sStimulusCode, $sClass) + { + if (is_null(self::$m_aCacheStimulusGrants)) + { + self::$m_aCacheStimulusGrants = array(); + $oFilterAll = new DBObjectSearch('URP_StimulusGrant'); + $oSet = new DBObjectSet($oFilterAll); + while ($oGrant = $oSet->Fetch()) + { + self::$m_aCacheStimulusGrants[$oGrant->Get('profileid').'-'.$oGrant->Get('stimulus').'-'.$oGrant->Get('class')] = $oGrant->GetKey(); + } + } + + $sCacheKey = "$iProfile-$sStimulusCode-$sClass"; + if (isset(self::$m_aCacheStimulusGrants[$sCacheKey])) + { + return self::$m_aCacheStimulusGrants[$sCacheKey]; + } + $oNewObj = MetaModel::NewObject("URP_StimulusGrant"); + $oNewObj->Set('profileid', $iProfile); + $oNewObj->Set('permission', 'yes'); + $oNewObj->Set('class', $sClass); + $oNewObj->Set('stimulus', $sStimulusCode); + $iId = $oNewObj->DBInsertNoReload(); + self::$m_aCacheStimulusGrants[$sCacheKey] = $iId; + return $iId; + } + + /* + * Create the built-in Administrator profile with its reserved name + */ + public static function DoCreateAdminProfile() + { + self::DoCreateProfile(ADMIN_PROFILE_NAME, 'Has the rights on everything (bypassing any control)', true /* reserved name */); + } + + /* + * Overload the standard behavior to preserve reserved names + */ + public function DoCheckToWrite() + { + parent::DoCheckToWrite(); + + if ($this->m_bCheckReservedNames) + { + $aChanges = $this->ListChanges(); + if (array_key_exists('name', $aChanges)) + { + if ($this->GetOriginal('name') == ADMIN_PROFILE_NAME) + { + $this->m_aCheckIssues[] = "The name of the Administrator profile must not be changed"; + } + elseif ($this->Get('name') == ADMIN_PROFILE_NAME) + { + $this->m_aCheckIssues[] = ADMIN_PROFILE_NAME." is a reserved to the built-in Administrator profile"; + } + elseif ($this->GetOriginal('name') == PORTAL_PROFILE_NAME) + { + $this->m_aCheckIssues[] = "The name of the User Portal profile must not be changed"; + } + elseif ($this->Get('name') == PORTAL_PROFILE_NAME) + { + $this->m_aCheckIssues[] = PORTAL_PROFILE_NAME." is a reserved to the built-in User Portal profile"; + } + } + } + } + + function GetGrantAsHtml($oUserRights, $sClass, $sAction) + { + $iGrant = $oUserRights->GetProfileActionGrant($this->GetKey(), $sClass, $sAction); + if (!is_null($iGrant)) + { + return ''.Dict::S('UI:UserManagement:ActionAllowed:Yes').''; + } + else + { + return ''.Dict::S('UI:UserManagement:ActionAllowed:No').''; + } + } + + function DoShowGrantSumary($oPage) + { + if ($this->GetRawName() == "Administrator") + { + // Looks dirty, but ok that's THE ONE + $oPage->p(Dict::S('UI:UserManagement:AdminProfile+')); + return; + } + + // Note: for sure, we assume that the instance is derived from UserRightsProfile + $oUserRights = UserRights::GetModuleInstance(); + + $aDisplayData = array(); + foreach (MetaModel::GetClasses('bizmodel') as $sClass) + { + // Skip non instantiable classes + if (MetaModel::IsAbstract($sClass)) continue; + + $aStimuli = array(); + foreach (MetaModel::EnumStimuli($sClass) as $sStimulusCode => $oStimulus) + { + $oGrant = $oUserRights->GetClassStimulusGrant($this->GetKey(), $sClass, $sStimulusCode); + if (is_object($oGrant) && ($oGrant->Get('permission') == 'yes')) + { + $aStimuli[] = ''.htmlentities($oStimulus->GetLabel(), ENT_QUOTES, 'UTF-8').''; + } + } + $sStimuli = implode(', ', $aStimuli); + + $aDisplayData[] = array( + 'class' => MetaModel::GetName($sClass), + 'read' => $this->GetGrantAsHtml($oUserRights, $sClass, 'Read'), + 'bulkread' => $this->GetGrantAsHtml($oUserRights, $sClass, 'Bulk Read'), + 'write' => $this->GetGrantAsHtml($oUserRights, $sClass, 'Modify'), + 'bulkwrite' => $this->GetGrantAsHtml($oUserRights, $sClass, 'Bulk Modify'), + 'delete' => $this->GetGrantAsHtml($oUserRights, $sClass, 'Delete'), + 'bulkdelete' => $this->GetGrantAsHtml($oUserRights, $sClass, 'Bulk Delete'), + 'stimuli' => $sStimuli, + ); + } + + $aDisplayConfig = array(); + $aDisplayConfig['class'] = array('label' => Dict::S('UI:UserManagement:Class'), 'description' => Dict::S('UI:UserManagement:Class+')); + $aDisplayConfig['read'] = array('label' => Dict::S('UI:UserManagement:Action:Read'), 'description' => Dict::S('UI:UserManagement:Action:Read+')); + $aDisplayConfig['bulkread'] = array('label' => Dict::S('UI:UserManagement:Action:BulkRead'), 'description' => Dict::S('UI:UserManagement:Action:BulkRead+')); + $aDisplayConfig['write'] = array('label' => Dict::S('UI:UserManagement:Action:Modify'), 'description' => Dict::S('UI:UserManagement:Action:Modify+')); + $aDisplayConfig['bulkwrite'] = array('label' => Dict::S('UI:UserManagement:Action:BulkModify'), 'description' => Dict::S('UI:UserManagement:Action:BulkModify+')); + $aDisplayConfig['delete'] = array('label' => Dict::S('UI:UserManagement:Action:Delete'), 'description' => Dict::S('UI:UserManagement:Action:Delete+')); + $aDisplayConfig['bulkdelete'] = array('label' => Dict::S('UI:UserManagement:Action:BulkDelete'), 'description' => Dict::S('UI:UserManagement:Action:BulkDelete+')); + $aDisplayConfig['stimuli'] = array('label' => Dict::S('UI:UserManagement:Action:Stimuli'), 'description' => Dict::S('UI:UserManagement:Action:Stimuli+')); + $oPage->table($aDisplayConfig, $aDisplayData); + } + + function DisplayBareRelations(WebPage $oPage, $bEditMode = false) + { + parent::DisplayBareRelations($oPage, $bEditMode); + if (!$bEditMode) + { + $oPage->SetCurrentTab(Dict::S('UI:UserManagement:GrantMatrix')); + $this->DoShowGrantSumary($oPage); + } + } +} + + + +class URP_UserProfile extends UserRightsBaseClassGUI +{ + public static function Init() + { + $aParams = array + ( + "category" => "addon/userrights", + "key_type" => "autoincrement", + "name_attcode" => "userid", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_urp_userprofile", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + 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()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("userlogin", array("allowed_values"=>null, "extkey_attcode"=> 'userid', "target_attcode"=>"login"))); + + 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()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("profile", array("allowed_values"=>null, "extkey_attcode"=> 'profileid', "target_attcode"=>"name"))); + + MetaModel::Init_AddAttribute(new AttributeString("reason", array("allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + + // Display lists + MetaModel::Init_SetZListItems('details', array('userid', 'profileid', 'reason')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('userid', 'profileid', 'reason')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('userid', 'profileid')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('userid', 'profileid')); // Criteria of the advanced search form + } + + public function GetName() + { + return Dict::Format('UI:UserManagement:LinkBetween_User_And_Profile', $this->Get('userlogin'), $this->Get('profile')); + } +} + +class URP_UserOrg extends UserRightsBaseClassGUI +{ + public static function Init() + { + $aParams = array + ( + "category" => "addon/userrights", + "key_type" => "autoincrement", + "name_attcode" => "userid", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_urp_userorg", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + 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()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("userlogin", array("allowed_values"=>null, "extkey_attcode"=> 'userid', "target_attcode"=>"login"))); + + 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()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("allowed_org_name", array("allowed_values"=>null, "extkey_attcode"=> 'allowed_org_id', "target_attcode"=>"name"))); + + MetaModel::Init_AddAttribute(new AttributeString("reason", array("allowed_values"=>null, "sql"=>"reason", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + + // Display lists + MetaModel::Init_SetZListItems('details', array('userid', 'allowed_org_id', 'reason')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('allowed_org_id', 'reason')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('userid', 'allowed_org_id')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('userid', 'allowed_org_id')); // Criteria of the advanced search form + } + + public function GetName() + { + return Dict::Format('UI:UserManagement:LinkBetween_User_And_Org', $this->Get('userlogin'), $this->Get('allowed_org_name')); + } +} + + +class URP_ActionGrant extends UserRightsBaseClass +{ + public static function Init() + { + $aParams = array + ( + "category" => "addon/userrights", + "key_type" => "autoincrement", + "name_attcode" => "profileid", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_urp_grant_actions", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + + // Common to all grant classes (could be factorized by class inheritence, but this has to be benchmarked) + MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid", array("targetclass"=>"URP_Profiles", "jointype"=> "", "allowed_values"=>null, "sql"=>"profileid", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("profile", array("allowed_values"=>null, "extkey_attcode"=> 'profileid', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeClass("class", array("class_category"=>"", "more_values"=>"", "sql"=>"class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + 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()))); + + MetaModel::Init_AddAttribute(new AttributeString("action", array("allowed_values"=>null, "sql"=>"action", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + + // Display lists + MetaModel::Init_SetZListItems('details', array('profileid', 'class', 'permission', 'action')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('class', 'permission', 'action')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('profileid', 'class', 'permission', 'action')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('profileid', 'class', 'permission', 'action')); // Criteria of the advanced search form + } +} + + +class URP_StimulusGrant extends UserRightsBaseClass +{ + public static function Init() + { + $aParams = array + ( + "category" => "addon/userrights", + "key_type" => "autoincrement", + "name_attcode" => "profileid", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_urp_grant_stimulus", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + + // Common to all grant classes (could be factorized by class inheritence, but this has to be benchmarked) + MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid", array("targetclass"=>"URP_Profiles", "jointype"=> "", "allowed_values"=>null, "sql"=>"profileid", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeExternalField("profile", array("allowed_values"=>null, "extkey_attcode"=> 'profileid', "target_attcode"=>"name"))); + MetaModel::Init_AddAttribute(new AttributeClass("class", array("class_category"=>"", "more_values"=>"", "sql"=>"class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + 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()))); + + MetaModel::Init_AddAttribute(new AttributeString("stimulus", array("allowed_values"=>null, "sql"=>"action", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array()))); + + // Display lists + MetaModel::Init_SetZListItems('details', array('profileid', 'class', 'permission', 'stimulus')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('class', 'permission', 'stimulus')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('profileid', 'class', 'permission', 'stimulus')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('profileid', 'class', 'permission', 'stimulus')); // Criteria of the advanced search form + } +} + + +class URP_AttributeGrant extends UserRightsBaseClass +{ + public static function Init() + { + $aParams = array + ( + "category" => "addon/userrights", + "key_type" => "autoincrement", + "name_attcode" => "actiongrantid", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_urp_grant_attributes", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "", + ); + MetaModel::Init_Params($aParams); + //MetaModel::Init_InheritAttributes(); + + MetaModel::Init_AddAttribute(new AttributeExternalKey("actiongrantid", array("targetclass"=>"URP_ActionGrant", "jointype"=> "", "allowed_values"=>null, "sql"=>"actiongrantid", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("attcode", array("allowed_values"=>null, "sql"=>"attcode", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); + + // Display lists + MetaModel::Init_SetZListItems('details', array('actiongrantid', 'attcode')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('attcode')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('actiongrantid', 'attcode')); // Criteria of the std search form + MetaModel::Init_SetZListItems('advanced_search', array('actiongrantid', 'attcode')); // Criteria of the advanced search form + } +} + + + + +class UserRightsProfile extends UserRightsAddOnAPI +{ + static public $m_aActionCodes = array( + UR_ACTION_READ => 'read', + UR_ACTION_MODIFY => 'modify', + UR_ACTION_DELETE => 'delete', + UR_ACTION_BULK_READ => 'bulk read', + UR_ACTION_BULK_MODIFY => 'bulk modify', + UR_ACTION_BULK_DELETE => 'bulk delete', + ); + + // Installation: create the very first user + public function CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage = 'EN US') + { + // Create a change to record the history of the User object + $oChange = MetaModel::NewObject("CMDBChange"); + $oChange->Set("date", time()); + $oChange->Set("userinfo", "Initialization"); + $iChangeId = $oChange->DBInsert(); + + $iContactId = 0; + // Support drastic data model changes: no organization class (or not writable)! + if (MetaModel::IsValidClass('Organization') && !MetaModel::IsAbstract('Organization')) + { + $oOrg = new Organization(); + $oOrg->Set('name', 'My Company/Department'); + $oOrg->Set('code', 'SOMECODE'); + $iOrgId = $oOrg->DBInsertTrackedNoReload($oChange, true /* skip security */); + + // Support drastic data model changes: no Person class (or not writable)! + if (MetaModel::IsValidClass('Person') && !MetaModel::IsAbstract('Person')) + { + $oContact = new Person(); + $oContact->Set('name', 'My last name'); + $oContact->Set('first_name', 'My first name'); + if (MetaModel::IsValidAttCode('Person', 'org_id')) + { + $oContact->Set('org_id', $iOrgId); + } + if (MetaModel::IsValidAttCode('Person', 'phone')) + { + $oContact->Set('phone', '+00 000 000 000'); + } + $oContact->Set('email', 'my.email@foo.org'); + $iContactId = $oContact->DBInsertTrackedNoReload($oChange, true /* skip security */); + } + } + + + $oUser = new UserLocal(); + $oUser->Set('login', $sAdminUser); + $oUser->Set('password', $sAdminPwd); + if (MetaModel::IsValidAttCode('UserLocal', 'contactid') && ($iContactId != 0)) + { + $oUser->Set('contactid', $iContactId); + } + $oUser->Set('language', $sLanguage); // Language was chosen during the installation + + // Add this user to the very specific 'admin' profile + $oAdminProfile = MetaModel::GetObjectFromOQL("SELECT URP_Profiles WHERE name = :name", array('name' => ADMIN_PROFILE_NAME), true /*all data*/); + if (is_object($oAdminProfile)) + { + $oUserProfile = new URP_UserProfile(); + //$oUserProfile->Set('userid', $iUserId); + $oUserProfile->Set('profileid', $oAdminProfile->GetKey()); + $oUserProfile->Set('reason', 'By definition, the administrator must have the administrator profile'); + //$oUserProfile->DBInsertTrackedNoReload($oChange, true /* skip security */); + $oSet = DBObjectSet::FromObject($oUserProfile); + $oUser->Set('profile_list', $oSet); + } + $iUserId = $oUser->DBInsertTrackedNoReload($oChange, true /* skip security */); + return true; + } + + public function Init() + { + } + + + protected $m_aAdmins = array(); // id -> bool, true if the user has the well-known admin profile + protected $m_aPortalUsers = array(); // id -> bool, true if the user has the well-known portal user profile + + protected $m_aProfiles; // id -> object + protected $m_aUserProfiles = array(); // userid,profileid -> object + protected $m_aUserOrgs = array(); // userid -> array of orgid + + // Those arrays could be completed on demand (inheriting parent permissions) + protected $m_aClassActionGrants = null; // profile, class, action -> actiongrantid (or false if NO, or null/missing if undefined) + protected $m_aClassStimulusGrants = array(); // profile, class, stimulus -> permission + + // Built on demand, could be optimized if necessary (doing a query for each attribute that needs to be read) + protected $m_aObjectActionGrants = array(); + + /** + * Read and cache organizations allowed to the given user + * + * @param oUser + * @param sClass -not used here but can be used in overloads + */ + protected function GetUserOrgs($oUser, $sClass) + { + $iUser = $oUser->GetKey(); + if (!array_key_exists($iUser, $this->m_aUserOrgs)) + { + $this->m_aUserOrgs[$iUser] = array(); + + $sHierarchicalKeyCode = MetaModel::IsHierarchicalClass('Organization'); + if ($sHierarchicalKeyCode !== false) + { + $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'; + $oUserOrgSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData($sUserOrgQuery), array(), array('userid' => $iUser)); + while ($aRow = $oUserOrgSet->FetchAssoc()) + { + $oUserOrg = $aRow['UserOrg']; + $oOrg = $aRow['Org']; + $this->m_aUserOrgs[$iUser][] = $oOrg->GetKey(); + } + } + else + { + $oSearch = new DBObjectSearch('URP_UserOrg'); + $oSearch->AllowAllData(); + $oCondition = new BinaryExpression(new FieldExpression('userid'), '=', new VariableExpression('userid')); + $oSearch->AddConditionExpression($oCondition); + + $oUserOrgSet = new DBObjectSet($oSearch, array(), array('userid' => $iUser)); + while ($oUserOrg = $oUserOrgSet->Fetch()) + { + $this->m_aUserOrgs[$iUser][] = $oUserOrg->Get('allowed_org_id'); + } + } + } + return $this->m_aUserOrgs[$iUser]; + } + + /** + * Read and cache profiles of the given user + */ + protected function GetUserProfiles($iUser) + { + if (!array_key_exists($iUser, $this->m_aUserProfiles)) + { + $oSearch = new DBObjectSearch('URP_UserProfile'); + $oSearch->AllowAllData(); + $oCondition = new BinaryExpression(new FieldExpression('userid'), '=', new VariableExpression('userid')); + $oSearch->AddConditionExpression($oCondition); + + $this->m_aUserProfiles[$iUser] = array(); + $oUserProfileSet = new DBObjectSet($oSearch, array(), array('userid' => $iUser)); + while ($oUserProfile = $oUserProfileSet->Fetch()) + { + $this->m_aUserProfiles[$iUser][$oUserProfile->Get('profileid')] = $oUserProfile; + } + } + return $this->m_aUserProfiles[$iUser]; + + } + + public function ResetCache() + { + // Loaded by Load cache + $this->m_aProfiles = null; + $this->m_aUserProfiles = array(); + $this->m_aUserOrgs = array(); + + $this->m_aAdmins = array(); + $this->m_aPortalUsers = array(); + + // Loaded on demand (time consuming as compared to the others) + $this->m_aClassActionGrants = null; + $this->m_aClassStimulusGrants = null; + + $this->m_aObjectActionGrants = array(); + } + + // Separate load: this cache is much more time consuming while loading + // Thus it is loaded iif required + // Could be improved by specifying the profile id + public function LoadActionGrantCache() + { + if (!is_null($this->m_aClassActionGrants)) return; + + $oKPI = new ExecutionKPI(); + + $oFilter = DBObjectSearch::FromOQL_AllData("SELECT URP_ActionGrant AS p WHERE p.permission = 'yes'"); + $aGrants = $oFilter->ToDataArray(); + foreach($aGrants as $aGrant) + { + $this->m_aClassActionGrants[$aGrant['profileid']][$aGrant['class']][strtolower($aGrant['action'])] = $aGrant['id']; + } + + $oKPI->ComputeAndReport('Load of action grants'); + } + + public function LoadCache() + { + if (!is_null($this->m_aProfiles)) return; + // Could be loaded in a shared memory (?) + + $oKPI = new ExecutionKPI(); + + if (self::HasSharing()) + { + SharedObject::InitSharedClassProperties(); + } + + $oProfileSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_Profiles")); + $this->m_aProfiles = array(); + while ($oProfile = $oProfileSet->Fetch()) + { + $this->m_aProfiles[$oProfile->GetKey()] = $oProfile; + } + + $this->m_aClassStimulusGrants = array(); + $oStimGrantSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_StimulusGrant")); + $this->m_aStimGrants = array(); + while ($oStimGrant = $oStimGrantSet->Fetch()) + { + $this->m_aClassStimulusGrants[$oStimGrant->Get('profileid')][$oStimGrant->Get('class')][$oStimGrant->Get('stimulus')] = $oStimGrant; + } + + $oKPI->ComputeAndReport('Load of user management cache (excepted Action Grants)'); + +/* + echo "
\n";
+		print_r($this->m_aProfiles);
+		print_r($this->m_aUserProfiles);
+		print_r($this->m_aUserOrgs);
+		print_r($this->m_aClassActionGrants);
+		print_r($this->m_aClassStimulusGrants);
+		echo "
\n"; +exit; +*/ + + return true; + } + + public function IsAdministrator($oUser) + { + //$this->LoadCache(); + $iUser = $oUser->GetKey(); + if (!array_key_exists($iUser, $this->m_aAdmins)) + { + $bIsAdmin = false; + foreach($this->GetUserProfiles($iUser) as $oUserProfile) + { + if ($oUserProfile->Get('profile') == ADMIN_PROFILE_NAME) + { + $bIsAdmin = true; + break; + } + } + $this->m_aAdmins[$iUser] = $bIsAdmin; + } + return $this->m_aAdmins[$iUser]; + } + + public function IsPortalUser($oUser) + { + //$this->LoadCache(); + $iUser = $oUser->GetKey(); + if (!array_key_exists($iUser, $this->m_aPortalUsers)) + { + $bIsPortalUser = false; + foreach($this->GetUserProfiles($iUser) as $oUserProfile) + { + if ($oUserProfile->Get('profile') == PORTAL_PROFILE_NAME) + { + $bIsPortalUser = true; + break; + } + } + $this->m_aPortalUsers[$iUser] = $bIsPortalUser; + } + return $this->m_aPortalUsers[$iUser]; + } + + public function GetSelectFilter($oUser, $sClass, $aSettings = array()) + { + $this->LoadCache(); + + $aObjectPermissions = $this->GetUserActionGrant($oUser, $sClass, UR_ACTION_READ); + if ($aObjectPermissions['permission'] == UR_ALLOWED_NO) + { + return false; + } + + // Determine how to position the objects of this class + // + $sAttCode = self::GetOwnerOrganizationAttCode($sClass); + if (is_null($sAttCode)) + { + // No filtering for this object + return true; + } + // Position the user + // + $aUserOrgs = $this->GetUserOrgs($oUser, $sClass); + if (count($aUserOrgs) == 0) + { + // No org means 'any org' + return true; + } + + $oExpression = new FieldExpression($sAttCode, $sClass); + $oFilter = new DBObjectSearch($sClass); + $oListExpr = ListExpression::FromScalars($aUserOrgs); + + $oCondition = new BinaryExpression($oExpression, 'IN', $oListExpr); + $oFilter->AddConditionExpression($oCondition); + + if (self::HasSharing()) + { + if (($sAttCode == 'id') && isset($aSettings['bSearchMode']) && $aSettings['bSearchMode']) + { + // Querying organizations (or derived) + // and the expected list of organizations will be used as a search criteria + // Therefore the query can also return organization having objects shared with the allowed organizations + // + // 1) build the list of organizations sharing something with the allowed organizations + // Organization <== sharing_org_id == SharedObject having org_id IN {user orgs} + $oShareSearch = new DBObjectSearch('SharedObject'); + $oOrgField = new FieldExpression('org_id', 'SharedObject'); + $oShareSearch->AddConditionExpression(new BinaryExpression($oOrgField, 'IN', $oListExpr)); + + $oSearchSharers = new DBObjectSearch('Organization'); + $oSearchSharers->AllowAllData(); + $oSearchSharers->AddCondition_ReferencedBy($oShareSearch, 'sharing_org_id'); + $aSharers = array(); + foreach($oSearchSharers->ToDataArray(array('id')) as $aRow) + { + $aSharers[] = $aRow['id']; + } + // 2) Enlarge the overall results: ... OR id IN(id1, id2, id3) + if (count($aSharers) > 0) + { + $oSharersList = ListExpression::FromScalars($aSharers); + $oFilter->MergeConditionExpression(new BinaryExpression($oExpression, 'IN', $oSharersList)); + } + } + + $aShareProperties = SharedObject::GetSharedClassProperties($sClass); + if ($aShareProperties) + { + $sShareClass = $aShareProperties['share_class']; + $sShareAttCode = $aShareProperties['attcode']; + + $oSearchShares = new DBObjectSearch($sShareClass); + $oSearchShares->AllowAllData(); + + $sHierarchicalKeyCode = MetaModel::IsHierarchicalClass('Organization'); + $oOrgField = new FieldExpression('org_id', $sShareClass); + $oSearchShares->AddConditionExpression(new BinaryExpression($oOrgField, 'IN', $oListExpr)); + $aShared = array(); + foreach($oSearchShares->ToDataArray(array($sShareAttCode)) as $aRow) + { + $aShared[] = $aRow[$sShareAttCode]; + } + if (count($aShared) > 0) + { + $oObjId = new FieldExpression('id', $sClass); + $oSharedIdList = ListExpression::FromScalars($aShared); + $oFilter->MergeConditionExpression(new BinaryExpression($oObjId, 'IN', $oSharedIdList)); + } + } + } // if HasSharing + + return $oFilter; + } + + // This verb has been made public to allow the development of an accurate feedback for the current configuration + public function GetProfileActionGrant($iProfile, $sClass, $sAction) + { + $this->LoadActionGrantCache(); + + // Note: action is forced lowercase to be more flexible (historical bug) + $sAction = strtolower($sAction); + if (isset($this->m_aClassActionGrants[$iProfile][$sClass][$sAction])) + { + return $this->m_aClassActionGrants[$iProfile][$sClass][$sAction]; + } + + // Recursively look for the grant record in the class hierarchy + $sParentClass = MetaModel::GetParentPersistentClass($sClass); + if (empty($sParentClass)) + { + $iGrant = null; + } + else + { + // Recursively look for the grant record in the class hierarchy + $iGrant = $this->GetProfileActionGrant($iProfile, $sParentClass, $sAction); + } + + $this->m_aClassActionGrants[$iProfile][$sClass][$sAction] = $iGrant; + return $iGrant; + } + + protected function GetUserActionGrant($oUser, $sClass, $iActionCode) + { + $this->LoadCache(); + + // load and cache permissions for the current user on the given class + // + $iUser = $oUser->GetKey(); + $aTest = @$this->m_aObjectActionGrants[$iUser][$sClass][$iActionCode]; + if (is_array($aTest)) return $aTest; + + $sAction = self::$m_aActionCodes[$iActionCode]; + + $iPermission = UR_ALLOWED_NO; + $aAttributes = array(); + foreach($this->GetUserProfiles($iUser) as $iProfile => $oProfile) + { + $iGrant = $this->GetProfileActionGrant($iProfile, $sClass, $sAction); + if (is_null($iGrant) || !$iGrant) + { + continue; // loop to the next profile + } + else + { + $iPermission = UR_ALLOWED_YES; + + // update the list of attributes with those allowed for this profile + // + $oSearch = DBObjectSearch::FromOQL_AllData("SELECT URP_AttributeGrant WHERE actiongrantid = :actiongrantid"); + $oSet = new DBObjectSet($oSearch, array(), array('actiongrantid' => $iGrant)); + $aProfileAttributes = $oSet->GetColumnAsArray('attcode', false); + if (count($aProfileAttributes) == 0) + { + $aAllAttributes = array_keys(MetaModel::ListAttributeDefs($sClass)); + $aAttributes = array_merge($aAttributes, $aAllAttributes); + } + else + { + $aAttributes = array_merge($aAttributes, $aProfileAttributes); + } + } + } + + $aRes = array( + 'permission' => $iPermission, + 'attributes' => $aAttributes, + ); + $this->m_aObjectActionGrants[$iUser][$sClass][$iActionCode] = $aRes; + return $aRes; + } + + public function IsActionAllowed($oUser, $sClass, $iActionCode, $oInstanceSet = null) + { + $this->LoadCache(); + + $aObjectPermissions = $this->GetUserActionGrant($oUser, $sClass, $iActionCode); + $iPermission = $aObjectPermissions['permission']; + + // Note: In most cases the object set is ignored because it was interesting to optimize for huge data sets + // and acceptable to consider only the root class of the object set + + if ($iPermission != UR_ALLOWED_YES) + { + // It is already NO for everyone... that's the final word! + } + elseif ($iActionCode == UR_ACTION_READ) + { + // We are protected by GetSelectFilter: the object set contains objects allowed or shared for reading + } + elseif ($iActionCode == UR_ACTION_BULK_READ) + { + // We are protected by GetSelectFilter: the object set contains objects allowed or shared for reading + } + elseif ($oInstanceSet) + { + // We are protected by GetSelectFilter: the object set contains objects allowed or shared for reading + // We have to answer NO for objects shared for reading purposes + if (self::HasSharing()) + { + $aClassProps = SharedObject::GetSharedClassProperties($sClass); + if ($aClassProps) + { + // This class is shared, GetSelectFilter may allow some objects for read only + // But currently we are checking wether the objects might be written... + // Let's exclude the objects based on the relevant criteria + + $sOrgAttCode = self::GetOwnerOrganizationAttCode($sClass); + if (!is_null($sOrgAttCode)) + { + $aUserOrgs = $this->GetUserOrgs($oUser, $sClass); + if (!is_null($aUserOrgs) && count($aUserOrgs) > 0) + { + $iCountNO = 0; + $iCountYES = 0; + $oInstanceSet->Rewind(); + while($oObject = $oInstanceSet->Fetch()) + { + $iOrg = $oObject->Get($sOrgAttCode); + if (in_array($iOrg, $aUserOrgs)) + { + $iCountYES++; + } + else + { + $iCountNO++; + } + } + if ($iCountNO == 0) + { + $iPermission = UR_ALLOWED_YES; + } + elseif ($iCountYES == 0) + { + $iPermission = UR_ALLOWED_NO; + } + else + { + $iPermission = UR_ALLOWED_DEPENDS; + } + } + } + } + } + } + return $iPermission; + } + + public function IsActionAllowedOnAttribute($oUser, $sClass, $sAttCode, $iActionCode, $oInstanceSet = null) + { + $this->LoadCache(); + + // Note: The object set is ignored because it was interesting to optimize for huge data sets + // and acceptable to consider only the root class of the object set + $aObjectPermissions = $this->GetUserActionGrant($oUser, $sClass, $iActionCode); + $aAttributes = $aObjectPermissions['attributes']; + if (in_array($sAttCode, $aAttributes)) + { + return $aObjectPermissions['permission']; + } + else + { + return UR_ALLOWED_NO; + } + } + + // This verb has been made public to allow the development of an accurate feedback for the current configuration + public function GetClassStimulusGrant($iProfile, $sClass, $sStimulusCode) + { + $this->LoadCache(); + + if (isset($this->m_aClassStimulusGrants[$iProfile][$sClass][$sStimulusCode])) + { + return $this->m_aClassStimulusGrants[$iProfile][$sClass][$sStimulusCode]; + } + else + { + return null; + } + } + + public function IsStimulusAllowed($oUser, $sClass, $sStimulusCode, $oInstanceSet = null) + { + $this->LoadCache(); + // Note: this code is VERY close to the code of IsActionAllowed() + $iUser = $oUser->GetKey(); + + // Note: The object set is ignored because it was interesting to optimize for huge data sets + // and acceptable to consider only the root class of the object set + $iPermission = UR_ALLOWED_NO; + foreach($this->GetUserProfiles($iUser) as $iProfile => $oProfile) + { + $oGrantRecord = $this->GetClassStimulusGrant($iProfile, $sClass, $sStimulusCode); + if (!is_null($oGrantRecord)) + { + // no need to fetch the record, we've requested the records having permission = 'yes' + $iPermission = UR_ALLOWED_YES; + } + } + return $iPermission; + } + + public function FlushPrivileges() + { + $this->ResetCache(); + } + + /** + * Find out which attribute is corresponding the the dimension 'owner org' + * returns null if no such attribute has been found (no filtering should occur) + */ + public static function GetOwnerOrganizationAttCode($sClass) + { + $sAttCode = null; + + $aCallSpec = array($sClass, 'MapContextParam'); + if (($sClass == 'Organization') || is_subclass_of($sClass, 'Organization')) + { + $sAttCode = 'id'; + } + elseif (is_callable($aCallSpec)) + { + $sAttCode = call_user_func($aCallSpec, 'org_id'); // Returns null when there is no mapping for this parameter + if (!MetaModel::IsValidAttCode($sClass, $sAttCode)) + { + // Skip silently. The data model checker will tell you something about this... + $sAttCode = null; + } + } + elseif(MetaModel::IsValidAttCode($sClass, 'org_id')) + { + $sAttCode = 'org_id'; + } + + return $sAttCode; + } + + /** + * Determine wether the objects can be shared by the mean of a class SharedObject + **/ + protected static function HasSharing() + { + static $bHasSharing; + if (!isset($bHasSharing)) + { + $bHasSharing = class_exists('SharedObject'); + } + return $bHasSharing; + } +} + + +UserRights::SelectModule('UserRightsProfile'); + +?>