Files
iTop/core/userrights.class.inc.php

1951 lines
53 KiB
PHP

<?php
/**
* Copyright (C) 2013-2020 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
class UserRightException extends CoreException
{
}
define('UR_ALLOWED_NO', 0);
define('UR_ALLOWED_YES', 1);
define('UR_ALLOWED_DEPENDS', 2);
define('UR_ACTION_READ', 1); // View an object
define('UR_ACTION_MODIFY', 2); // Create/modify an object/attribute
define('UR_ACTION_DELETE', 3); // Delete an object
define('UR_ACTION_BULK_READ', 4); // Export multiple objects
define('UR_ACTION_BULK_MODIFY', 5); // Create/modify multiple objects
define('UR_ACTION_BULK_DELETE', 6); // Delete multiple objects
define('UR_ACTION_CREATE', 7); // Instantiate an object
define('UR_ACTION_APPLICATION_DEFINED', 10000); // Application specific actions (CSV import, View schema...)
/**
* User management module API
*
* @package iTopORM
*/
abstract class UserRightsAddOnAPI
{
/**
* @param string $sAdminUser
* @param string $sAdminPwd
* @param string $sLanguage
*
* @return mixed
*/
abstract public function CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage = 'EN US'); // could be used during initial installation
/**
* @return void
*/
abstract public function Init(); // loads data (possible optimizations)
/**
* Used to build select queries showing only objects visible for the given user
*
* @param string $sLogin
* @param string $sClass
* @param array $aSettings
*
* @return mixed
*/
abstract public function GetSelectFilter($sLogin, $sClass, $aSettings = array()); // returns a filter object
/**
* @param \User $oUser
* @param string $sClass
* @param int $iActionCode
* @param null $oInstanceSet
*
* @return bool
*/
abstract public function IsActionAllowed($oUser, $sClass, $iActionCode, $oInstanceSet = null);
/**
* @param \User $oUser
* @param string $sClass
* @param string $sStimulusCode
* @param \DBObjectSet|null $oInstanceSet
*
* @return bool
*/
abstract public function IsStimulusAllowed($oUser, $sClass, $sStimulusCode, $oInstanceSet = null);
/**
* @param \User $oUser
* @param string $sClass
* @param string $sAttCode
* @param int $iActionCode
* @param \DBObjectSet|null $oInstanceSet
*
* @return bool
*/
abstract public function IsActionAllowedOnAttribute($oUser, $sClass, $sAttCode, $iActionCode, $oInstanceSet = null);
/**
* @param \User $oUser
*
* @return bool
*/
abstract public function IsAdministrator($oUser);
/**
* @param \User $oUser
*
* @return bool
*/
abstract public function IsPortalUser($oUser);
/**
* @return void
*/
abstract public function FlushPrivileges();
/**
* Default behavior for addons that do not support profiles
*
* @param \User $oUser
* @return array
*/
public function ListProfiles($oUser)
{
return array();
}
/**
* ...
*
* @param string$sClass
* @param array $aAllowedOrgs
* @param array $aSettings
* @param string|null $sAttCode
*
* @return \DBObjectSearch
* @throws \CoreException
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
*/
public function MakeSelectFilter($sClass, $aAllowedOrgs, $aSettings = array(), $sAttCode = null)
{
if ($sAttCode == null)
{
$sAttCode = $this->GetOwnerOrganizationAttCode($sClass);
}
if (empty($sAttCode))
{
return $oFilter = new DBObjectSearch($sClass);
}
$oExpression = new FieldExpression($sAttCode, $sClass);
$oFilter = new DBObjectSearch($sClass);
$oListExpr = ListExpression::FromScalars($aAllowedOrgs);
$oCondition = new BinaryExpression($oExpression, 'IN', $oListExpr);
$oFilter->AddConditionExpression($oCondition);
if ($this->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;
}
}
require_once(APPROOT.'/application/cmdbabstract.class.inc.php');
abstract class User extends cmdbAbstractObject
{
/**
* @throws \CoreException
*/
public static function Init()
{
$aParams = array
(
"category" => "core,grant_by_profile,silo",
"key_type" => "autoincrement",
"name_attcode" => "login",
"state_attcode" => "",
"reconc_keys" => array(),
"db_table" => "priv_user",
"db_key_field" => "id",
"db_finalclass_field" => "",
);
MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeExternalKey("contactid", array("targetclass"=>"Person", "allowed_values"=>null, "sql"=>"contactid", "is_null_allowed"=>true, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeExternalField("last_name", array("allowed_values"=>null, "extkey_attcode"=> 'contactid', "target_attcode"=>"name")));
MetaModel::Init_AddAttribute(new AttributeExternalField("first_name", array("allowed_values"=>null, "extkey_attcode"=> 'contactid', "target_attcode"=>"first_name")));
MetaModel::Init_AddAttribute(new AttributeExternalField("email", array("allowed_values"=>null, "extkey_attcode"=> 'contactid', "target_attcode"=>"email")));
MetaModel::Init_AddAttribute(new AttributeExternalField("org_id", array("allowed_values"=>null, "extkey_attcode"=> 'contactid', "target_attcode"=>"org_id")));
MetaModel::Init_AddAttribute(new AttributeString("login", array("allowed_values"=>null, "sql"=>"login", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeApplicationLanguage("language", array("sql"=>"language", "default_value"=>"EN US", "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values" => new ValueSetEnum('enabled,disabled'), "sql"=>"status", "default_value"=>"enabled", "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("profile_list", array("linked_class"=>"URP_UserProfile", "ext_key_to_me"=>"userid", "ext_key_to_remote"=>"profileid", "allowed_values"=>null, "count_min"=>1, "count_max"=>0, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("allowed_org_list", array("linked_class"=>"URP_UserOrg", "ext_key_to_me"=>"userid", "ext_key_to_remote"=>"allowed_org_id", "allowed_values"=>null, "count_min"=>1, "count_max"=>0, "depends_on"=>array())));
// Display lists
MetaModel::Init_SetZListItems('details', array('contactid', 'org_id', 'email', 'login', 'language', 'status', 'profile_list', 'allowed_org_list')); // Unused as it's an abstract class !
MetaModel::Init_SetZListItems('list', array('finalclass', 'first_name', 'last_name', 'status', 'org_id')); // Attributes to be displayed for a list
// Search criteria
MetaModel::Init_SetZListItems('standard_search', array('login', 'contactid', 'email', 'language', 'status', 'org_id')); // Criteria of the std search form
MetaModel::Init_SetZListItems('default_search', array('login', 'contactid', 'org_id')); // Default criteria of the search banner
}
abstract public function CheckCredentials($sPassword);
abstract public function TrustWebServerContext();
abstract public function CanChangePassword();
abstract public function ChangePassword($sOldPassword, $sNewPassword);
/*
* Compute a name in best effort mode
*/
public function GetFriendlyName()
{
if (!MetaModel::IsValidAttCode(get_class($this), 'contactid'))
{
return $this->Get('login');
}
if ($this->Get('contactid') != 0)
{
$sFirstName = $this->Get('first_name');
$sLastName = $this->Get('last_name');
$sEmail = $this->Get('email');
if (strlen($sFirstName) > 0)
{
return "$sFirstName $sLastName";
}
elseif (strlen($sEmail) > 0)
{
return "$sLastName <$sEmail>";
}
else
{
return $sLastName;
}
}
return $this->Get('login');
}
protected $oContactObject;
/**
* Fetch and memorize the associated contact (if any)
*/
public function GetContactObject()
{
if (is_null($this->oContactObject))
{
if (MetaModel::IsValidAttCode(get_class($this), 'contactid') && ($this->Get('contactid') != 0))
{
$this->oContactObject = MetaModel::GetObject('Contact', $this->Get('contactid'));
}
}
return $this->oContactObject;
}
/**
* Overload the standard behavior.
*
* @throws \CoreException
*/
public function DoCheckToWrite()
{
parent::DoCheckToWrite();
// Note: This MUST be factorized later: declare unique keys (set of columns) in the data model
$aChanges = $this->ListChanges();
if (array_key_exists('login', $aChanges))
{
if (strcasecmp($this->Get('login'), $this->GetOriginal('login')) !== 0)
{
$sNewLogin = $aChanges['login'];
$oSearch = DBObjectSearch::FromOQL_AllData("SELECT User WHERE login = :newlogin");
if (!$this->IsNew())
{
$oSearch->AddCondition('id', $this->GetKey(), '!=');
}
$oSet = new DBObjectSet($oSearch, array(), array('newlogin' => $sNewLogin));
if ($oSet->Count() > 0)
{
$this->m_aCheckIssues[] = Dict::Format('Class:User/Error:LoginMustBeUnique', $sNewLogin);
}
}
}
// Check that this user has at least one profile assigned when profiles have changed
if (array_key_exists('profile_list', $aChanges))
{
$oSet = $this->Get('profile_list');
if ($oSet->Count() == 0)
{
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:AtLeastOneProfileIsNeeded');
}
}
// Only administrators can manage administrators
if (UserRights::IsAdministrator($this) && !UserRights::IsAdministrator())
{
$this->m_aCheckIssues[] = Dict::S('UI:Login:Error:AccessRestricted');
}
if (!UserRights::IsAdministrator())
{
$oUser = UserRights::GetUserObject();
$oAddon = UserRights::GetModuleInstance();
if (!is_null($oUser) && method_exists($oAddon, 'GetUserOrgs'))
{
if ((empty($this->GetOriginal('contactid')) && !($this->IsNew())) || empty($this->Get('contactid')))
{
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:PersonIsMandatory');
}
else
{
$aOrgs = $oAddon->GetUserOrgs($oUser, '');
if (count($aOrgs) > 0)
{
// Check that the modified User belongs to one of our organization
if (!in_array($this->GetOriginal('org_id'), $aOrgs) && !in_array($this->Get('org_id'), $aOrgs))
{
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:UserOrganizationNotAllowed');
}
// Check users with restricted organizations when allowed organizations have changed
if ($this->IsNew() || array_key_exists('allowed_org_list', $aChanges))
{
$oSet = $this->get('allowed_org_list');
if ($oSet->Count() == 0)
{
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:AtLeastOneOrganizationIsNeeded');
}
else
{
$aModifiedLinks = $oSet->ListModifiedLinks();
foreach ($aModifiedLinks as $oLink)
{
if (!in_array($oLink->Get('allowed_org_id'), $aOrgs))
{
$this->m_aCheckIssues[] = Dict::S('Class:User/Error:OrganizationNotAllowed');
}
}
}
}
}
}
}
}
}
function GetGrantAsHtml($sClass, $iAction)
{
if (UserRights::IsActionAllowed($sClass, $iAction, null, $this))
{
return '<span style="background-color: #ddffdd;">'.Dict::S('UI:UserManagement:ActionAllowed:Yes').'</span>';
}
else
{
return '<span style="background-color: #ffdddd;">'.Dict::S('UI:UserManagement:ActionAllowed:No').'</span>';
}
}
function DoShowGrantSumary($oPage, $sClassCategory)
{
if (UserRights::IsAdministrator($this))
{
// Looks dirty, but ok that's THE ONE
$oPage->p(Dict::S('UI:UserManagement:AdminProfile+'));
return;
}
$oKPI = new ExecutionKPI();
$aDisplayData = array();
foreach (MetaModel::GetClasses($sClassCategory) as $sClass)
{
$aClassStimuli = MetaModel::EnumStimuli($sClass);
if (count($aClassStimuli) > 0)
{
$aStimuli = array();
foreach ($aClassStimuli as $sStimulusCode => $oStimulus)
{
if (UserRights::IsStimulusAllowed($sClass, $sStimulusCode, null, $this))
{
$aStimuli[] = '<span title="'.$sStimulusCode.': '.htmlentities($oStimulus->GetDescription(), ENT_QUOTES, 'UTF-8').'">'.htmlentities($oStimulus->GetLabel(), ENT_QUOTES, 'UTF-8').'</span>';
}
}
$sStimuli = implode(', ', $aStimuli);
}
else
{
$sStimuli = '<em title="'.Dict::S('UI:UserManagement:NoLifeCycleApplicable+').'">'.Dict::S('UI:UserManagement:NoLifeCycleApplicable').'</em>';
}
$aDisplayData[] = array(
'class' => MetaModel::GetName($sClass),
'read' => $this->GetGrantAsHtml($sClass, UR_ACTION_READ),
'bulkread' => $this->GetGrantAsHtml($sClass, UR_ACTION_BULK_READ),
'write' => $this->GetGrantAsHtml($sClass, UR_ACTION_MODIFY),
'bulkwrite' => $this->GetGrantAsHtml($sClass, UR_ACTION_BULK_MODIFY),
'delete' => $this->GetGrantAsHtml($sClass, UR_ACTION_DELETE),
'bulkdelete' => $this->GetGrantAsHtml($sClass, UR_ACTION_BULK_DELETE),
'stimuli' => $sStimuli,
);
}
$oKPI->ComputeAndReport('Computation of user rights');
$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('UI:UserManagement:GrantMatrix');
$this->DoShowGrantSumary($oPage, 'bizmodel,grant_by_profile');
// debug
if (false)
{
$oPage->SetCurrentTab('More on user rigths (dev only)');
$oPage->add("<h3>User rights</h3>\n");
$this->DoShowGrantSumary($oPage, 'addon/userrights');
$oPage->add("<h3>Change log</h3>\n");
$this->DoShowGrantSumary($oPage, 'core/cmdb');
$oPage->add("<h3>Application</h3>\n");
$this->DoShowGrantSumary($oPage, 'application');
$oPage->add("<h3>GUI</h3>\n");
$this->DoShowGrantSumary($oPage, 'gui');
}
}
}
public function CheckToDelete(&$oDeletionPlan)
{
if (MetaModel::GetConfig()->Get('demo_mode'))
{
// Users deletion is NOT allowed in demo mode
$oDeletionPlan->AddToDelete($this, null);
$oDeletionPlan->SetDeletionIssues($this, array('deletion not allowed in demo mode.'), true);
$oDeletionPlan->ComputeResults();
return false;
}
return parent::CheckToDelete($oDeletionPlan);
}
protected function DBDeleteSingleObject()
{
if (MetaModel::GetConfig()->Get('demo_mode'))
{
// Users deletion is NOT allowed in demo mode
return;
}
parent::DBDeleteSingleObject();
}
}
/**
* Abstract class for all types of "internal" authentication i.e. users
* for which the application is supplied a login and a password opposed
* to "external" users for whom the authentication is performed outside
* of the application (by the web server for example).
* Note that "internal" users do not necessary correspond to a local authentication
* they may be authenticated by a remote system, like in authent-ldap.
*/
abstract class UserInternal extends User
{
// Nothing special, just a base class to categorize this type of authenticated users
public static function Init()
{
$aParams = array
(
"category" => "core,grant_by_profile",
"key_type" => "autoincrement",
"name_attcode" => "login",
"state_attcode" => "",
"reconc_keys" => array('login'),
"db_table" => "priv_internaluser",
"db_key_field" => "id",
"db_finalclass_field" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
// When set, this token allows for password reset
MetaModel::Init_AddAttribute(new AttributeOneWayPassword("reset_pwd_token", array("allowed_values"=>null, "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
// Display lists
MetaModel::Init_SetZListItems('details', array('contactid', 'org_id', 'email', 'login', 'status', 'language', 'profile_list', 'allowed_org_list')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('finalclass', 'first_name', 'last_name', 'status', 'org_id')); // Attributes to be displayed for a list
// Search criteria
MetaModel::Init_SetZListItems('standard_search', array('login', 'contactid', 'status', 'org_id')); // Criteria of the std search form
}
/**
* Use with care!
*/
public function SetPassword($sNewPassword)
{
}
/**
* The email recipient is the person who is allowed to regain control when the password gets lost
* Throws an exception if the feature cannot be available
*/
public function GetResetPasswordEmail()
{
if (!MetaModel::IsValidAttCode(get_class($this), 'contactid'))
{
throw new Exception(Dict::S('UI:ResetPwd-Error-NoContact'));
}
$iContactId = $this->Get('contactid');
if ($iContactId == 0)
{
throw new Exception(Dict::S('UI:ResetPwd-Error-NoContact'));
}
$oContact = MetaModel::GetObject('Contact', $iContactId);
// Determine the email attribute (the first one will be our choice)
foreach (MetaModel::ListAttributeDefs(get_class($oContact)) as $sAttCode => $oAttDef)
{
if ($oAttDef instanceof AttributeEmailAddress)
{
$sEmailAttCode = $sAttCode;
// we've got one, exit the loop
break;
}
}
if (!isset($sEmailAttCode))
{
throw new Exception(Dict::S('UI:ResetPwd-Error-NoEmailAtt'));
}
$sRes = trim($oContact->Get($sEmailAttCode));
return $sRes;
}
}
/**
* Self register extension
*
* @package iTopORM
*/
interface iSelfRegister
{
/**
* Called when no user is found in iTop for the corresponding 'name'. This method
* can create/synchronize the User in iTop with an external source (such as AD/LDAP) on the fly
* @param string $sName The typed-in user name
* @param string $sPassword The typed-in password
* @param string $sLoginMode The login method used (cas|form|basic|url)
* @param string $sAuthentication The authentication method used (any|internal|external)
* @return bool true if the user is a valid one, false otherwise
*/
public static function CheckCredentialsAndCreateUser($sName, $sPassword, $sLoginMode, $sAuthentication);
/**
* Called after the user has been authenticated and found in iTop. This method can
* Update the user's definition on the fly (profiles...) to keep it in sync with an external source
* @param User $oUser The user to update/synchronize
* @param string $sLoginMode The login mode used (cas|form|basic|url)
* @param string $sAuthentication The authentication method used
* @return void
*/
public static function UpdateUser(User $oUser, $sLoginMode, $sAuthentication);
}
/**
* User management core API
*
* @package iTopORM
*/
class UserRights
{
const DEFAULT_USER_CONTACT_ID_ATTCODE = 'contactid';
const DEFAULT_CONTACT_ORG_ID_ATTCODE = 'org_id';
const DEFAULT_CONTACT_ORG_ID_FRIENDLYNAME_ATTCODE = self::DEFAULT_CONTACT_ORG_ID_ATTCODE.'_friendlyname';
const DEFAULT_CONTACT_FIRSTNAME_ATTCODE = 'first_name';
const DEFAULT_CONTACT_PICTURE_ATTCODE = 'picture';
public static $m_aCacheUsers;
/** @var UserRightsAddOnAPI $m_oAddOn */
protected static $m_oAddOn;
protected static $m_oUser;
protected static $m_oRealUser;
protected static $m_sSelfRegisterAddOn = null;
protected static $m_aAdmins = array();
protected static $m_aPortalUsers = array();
/** @var array array('sName' => $sName, 'bSuccess' => $bSuccess); */
private static $m_sLastLoginStatus = null;
/**
* @param string $sModuleName
*
* @return void
* @throws \CoreException
*/
public static function SelectModule($sModuleName)
{
if (!class_exists($sModuleName))
{
throw new CoreException("Could not select this module, '$sModuleName' in not a valid class name");
}
if (!is_subclass_of($sModuleName, 'UserRightsAddOnAPI'))
{
throw new CoreException("Could not select this module, the class '$sModuleName' is not derived from UserRightsAddOnAPI");
}
self::$m_oAddOn = new $sModuleName;
self::$m_oAddOn->Init();
self::$m_oUser = null;
self::$m_oRealUser = null;
}
/**
* @param string $sModuleName
*
* @return void
* @throws \CoreException
*/
public static function SelectSelfRegister($sModuleName)
{
if (!class_exists($sModuleName))
{
throw new CoreException("Could not select the class, '$sModuleName' for self register, is not a valid class name");
}
self::$m_sSelfRegisterAddOn = $sModuleName;
}
/**
* @return \UserRightsAddOnAPI
*/
public static function GetModuleInstance()
{
return self::$m_oAddOn;
}
/**
* Installation: create the very first user
*
* @param string $sAdminUser
* @param string $sAdminPwd
* @param string $sLanguage
*
* @return bool
*/
public static function CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage = 'EN US')
{
$bRes = self::$m_oAddOn->CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage);
self::FlushPrivileges(true /* reset admin cache */);
return $bRes;
}
/**
* @return bool
*/
public static function IsLoggedIn()
{
if (self::$m_oUser == null)
{
return false;
}
else
{
return true;
}
}
/**
* @param string $sName
* @param string $sAuthentication
*
* @return bool
* @throws \DictExceptionUnknownLanguage
* @throws \OQLException
*/
public static function Login($sName, $sAuthentication = 'any')
{
$oUser = self::FindUser($sName, $sAuthentication);
if (is_null($oUser))
{
return false;
}
self::$m_oUser = $oUser;
if (isset($_SESSION['impersonate_user']))
{
self::$m_oRealUser = self::$m_oUser;
self::$m_oUser = self::FindUser($_SESSION['impersonate_user']);
}
Dict::SetUserLanguage(self::GetUserLanguage());
return true;
}
/**
* @param string $sName
* @param string $sPassword
* @param string $sLoginMode
* @param string $sAuthentication
*
* @return bool
* @throws \OQLException
*/
public static function CheckCredentials($sName, $sPassword, $sLoginMode = 'form', $sAuthentication = 'any')
{
$oUser = self::FindUser($sName, $sAuthentication);
if (is_null($oUser))
{
// Check if the user does not exist at all or if it is just disabled
if (self::FindUser($sName, $sAuthentication, true) == null)
{
// User does not exist at all
$bCheckCredentialsAndCreateUser = self::CheckCredentialsAndCreateUser($sName, $sPassword, $sLoginMode, $sAuthentication);
self::$m_sLastLoginStatus = array('sName' => $sName, 'bSuccess' => $bCheckCredentialsAndCreateUser);
return $bCheckCredentialsAndCreateUser;
}
else
{
// User is actually disabled
self::$m_sLastLoginStatus = array('sName' => $sName, 'bSuccess' => false);
return false;
}
}
if (!$oUser->CheckCredentials($sPassword))
{
self::$m_sLastLoginStatus = array('sName' => $sName, 'bSuccess' => false);
return false;
}
self::UpdateUser($oUser, $sLoginMode, $sAuthentication);
self::$m_sLastLoginStatus = array('sName' => $sName, 'bSuccess' => true);
return true;
}
/**
* @param string $sName
* @param string $sPassword
* @param string $sLoginMode
* @param string $sAuthentication
*
* @return mixed
*/
public static function CheckCredentialsAndCreateUser($sName, $sPassword, $sLoginMode, $sAuthentication)
{
if (self::$m_sSelfRegisterAddOn != null)
{
return call_user_func(array(self::$m_sSelfRegisterAddOn, 'CheckCredentialsAndCreateUser'), $sName, $sPassword, $sLoginMode, $sAuthentication);
}
}
/**
* @param \User $oUser
* @param string $sLoginMode
* @param string $sAuthentication
*/
public static function UpdateUser($oUser, $sLoginMode, $sAuthentication)
{
if (self::$m_sSelfRegisterAddOn != null)
{
call_user_func(array(self::$m_sSelfRegisterAddOn, 'UpdateUser'), $oUser, $sLoginMode, $sAuthentication);
}
}
/**
* @return bool
*/
public static function TrustWebServerContext()
{
if (!is_null(self::$m_oUser))
{
return self::$m_oUser->TrustWebServerContext();
}
else
{
return false;
}
}
/**
* Tells whether or not the archive mode is allowed to the current user
*
* @return bool
* @throws \CoreException
*/
public static function CanBrowseArchive()
{
if (is_null(self::$m_oUser))
{
$bRet = false;
}
elseif (isset($_SESSION['archive_allowed']))
{
$bRet = $_SESSION['archive_allowed'];
}
else
{
// As of now, anybody can switch to the archive mode as soon as there is an archivable class
$bRet = (count(MetaModel::EnumArchivableClasses()) > 0);
$_SESSION['archive_allowed'] = $bRet;
}
return $bRet;
}
/**
* @return bool
*/
public static function CanChangePassword()
{
if (MetaModel::DBIsReadOnly())
{
return false;
}
if (!is_null(self::$m_oUser))
{
return self::$m_oUser->CanChangePassword();
}
else
{
return false;
}
}
/**
* @param string $sOldPassword
* @param string $sNewPassword
* @param string $sName
*
* @return bool
* @throws \OQLException
*/
public static function ChangePassword($sOldPassword, $sNewPassword, $sName = '')
{
if (empty($sName))
{
$oUser = self::$m_oUser;
}
else
{
// find the id out of the login string
$oUser = self::FindUser($sName);
}
if (is_null($oUser))
{
return false;
}
else
{
$oUser->AllowWrite(true);
return $oUser->ChangePassword($sOldPassword, $sNewPassword);
}
}
/**
* @param string $sName Login identifier of the user to impersonate
*
* @return bool True if an impersonation occurred
* @throws \DictExceptionUnknownLanguage
* @throws \OQLException
*/
public static function Impersonate($sName)
{
if (!self::CheckLogin()) return false;
$bRet = false;
$oUser = self::FindUser($sName);
if ($oUser)
{
$bRet = true;
if (is_null(self::$m_oRealUser))
{
// First impersonation
self::$m_oRealUser = self::$m_oUser;
}
if (self::$m_oRealUser && (self::$m_oRealUser->GetKey() == $oUser->GetKey()))
{
// Equivalent to "Deimpersonate"
self::Deimpersonate();
}
else
{
// Do impersonate!
self::$m_oUser = $oUser;
Dict::SetUserLanguage(self::GetUserLanguage());
$_SESSION['impersonate_user'] = $sName;
self::_ResetSessionCache();
}
}
return $bRet;
}
/**
* @throws \DictExceptionUnknownLanguage
*/
public static function Deimpersonate()
{
if (!is_null(self::$m_oRealUser))
{
self::$m_oUser = self::$m_oRealUser;
Dict::SetUserLanguage(self::GetUserLanguage());
unset($_SESSION['impersonate_user']);
self::_ResetSessionCache();
}
}
/**
* @return string
*/
public static function GetUser()
{
if (is_null(self::$m_oUser))
{
return '';
}
else
{
return self::$m_oUser->Get('login');
}
}
/**
* @return \User|null
*/
public static function GetUserObject()
{
if (is_null(self::$m_oUser))
{
return null;
}
else
{
return self::$m_oUser;
}
}
/**
* @return string
*/
public static function GetUserLanguage()
{
if (is_null(self::$m_oUser))
{
return 'EN US';
}
else
{
return self::$m_oUser->Get('language');
}
}
/**
* @param string $sName
*
* @return string|null
*/
public static function GetUserId($sName = '')
{
if (empty($sName))
{
// return current user id
if (is_null(self::$m_oUser))
{
return null;
}
return self::$m_oUser->GetKey();
}
else
{
// find the id out of the login string
$oUser = self::$m_oAddOn->FindUser($sName);
if (is_null($oUser))
{
return null;
}
return $oUser->GetKey();
}
}
/**
* @param string $sName
*
* @return string
* @throws \Exception
*/
public static function GetContactId($sName = '')
{
if (empty($sName))
{
$oUser = self::$m_oUser;
}
else
{
$oUser = FindUser($sName);
}
if (is_null($oUser))
{
return '';
}
if (!MetaModel::IsValidAttCode(get_class($oUser), static::DEFAULT_USER_CONTACT_ID_ATTCODE))
{
return '';
}
return $oUser->Get(static::DEFAULT_USER_CONTACT_ID_ATTCODE);
}
/**
* Return the absolute URL of the contact picture
*
* @param string $sName
*
* @return mixed|string
* @throws \Exception
* @since 2.8.0
*/
public static function GetContactPicture($sName = '')
{
// TODO: replace with console one
$sPictureUrl = utils::GetAbsoluteUrlAppRoot().'images/user-pictures/' . appUserPreferences::GetPref('user_picture_placeholder', 'default-placeholder.png');
if (empty($sName))
{
$oUser = self::$m_oUser;
}
else
{
$oUser = FindUser($sName);
}
// Check that user exists (in case we try to get it for another contact)
if (!is_null($oUser) && !MetaModel::IsValidAttCode(get_class($oUser), static::DEFAULT_USER_CONTACT_ID_ATTCODE))
{
$oContact = $oUser->Get(static::DEFAULT_USER_CONTACT_ID_ATTCODE);
$sContactClass = get_class($oContact);
// Check that user has a contact
if(!is_null($oContact) && !MetaModel::IsValidAttCode($sContactClass, static::DEFAULT_CONTACT_PICTURE_ATTCODE))
{
/** @var \ormDocument $oPicture */
$oPicture = $oContact->Get(static::DEFAULT_CONTACT_PICTURE_ATTCODE);
if($oPicture->IsEmpty())
{
/** @var \AttributeImage $oAttDef */
$oAttDef = MetaModel::GetAttributeDef($sContactClass, static::DEFAULT_CONTACT_PICTURE_ATTCODE);
$sPictureUrl = $oAttDef->Get('default_image');
}
else
{
$sPictureUrl = $oPicture->GetDisplayURL($sContactClass, $oContact->GetKey(), static::DEFAULT_CONTACT_PICTURE_ATTCODE);
}
}
}
return $sPictureUrl;
}
/**
* Return the organization name of the current user's contact.
* If the user has no contact linked, null is returned.
*
* @return string|null
* @throws \Exception
* @since 2.8.0
*/
public static function GetContactOrganizationFriendlyname()
{
$sOrgFriendlyname = null;
$oContact = static::GetContactObject();
if(!is_null($oContact) && MetaModel::IsValidAttCode(get_class($oContact), static::DEFAULT_CONTACT_ORG_ID_FRIENDLYNAME_ATTCODE))
{
$sOrgFriendlyname = $oContact->Get(static::DEFAULT_CONTACT_ORG_ID_FRIENDLYNAME_ATTCODE);
}
return $sOrgFriendlyname;
}
/**
* Return the first name of the current user's contact.
* If the user has no contact, null is returned.
*
* @return string|null
* @throws \Exception
* @since 2.8.0
*/
public static function GetContactFirstname()
{
$sFirstname = null;
$oContact = static::GetContactObject();
if(!is_null($oContact) && MetaModel::IsValidAttCode(get_class($oContact), static::DEFAULT_CONTACT_FIRSTNAME_ATTCODE))
{
$sFirstname = $oContact->Get(static::DEFAULT_CONTACT_FIRSTNAME_ATTCODE);
}
return $sFirstname;
}
/**
* Return the friendlyname of the current user's contact.
* If the user has no contact, null is returned.
*
* @return string|null
* @throws \Exception
* @since 2.8.0
*/
public static function GetContactFriendlyname()
{
$sFriendlyname = null;
$oContact = static::GetContactObject();
if(!is_null($oContact) && MetaModel::IsValidAttCode(get_class($oContact), 'friendlyname'))
{
$sFriendlyname = $oContact->Get('friendlyname');
}
return $sFriendlyname;
}
/**
* @return \DBObject|null
*/
public static function GetContactObject()
{
if (is_null(self::$m_oUser))
{
return null;
}
else
{
return self::$m_oUser->GetContactObject();
}
}
/**
* Render the user name in best effort mode
*
* @param string $sName
*
* @return string
*/
public static function GetUserFriendlyName($sName = '')
{
if (empty($sName))
{
$oUser = self::$m_oUser;
}
else
{
$oUser = FindUser($sName);
}
if (is_null($oUser))
{
return '';
}
return $oUser->GetFriendlyName();
}
/**
* @return bool
*/
public static function IsImpersonated()
{
if (is_null(self::$m_oRealUser))
{
return false;
}
return true;
}
/**
* @return string
*/
public static function GetRealUser()
{
if (is_null(self::$m_oRealUser))
{
return '';
}
return self::$m_oRealUser->Get('login');
}
/**
* @return \User|null
*/
public static function GetRealUserObject()
{
return self::$m_oRealUser;
}
/**
* @return string
*/
public static function GetRealUserId()
{
if (is_null(self::$m_oRealUser))
{
return '';
}
return self::$m_oRealUser->GetKey();
}
/**
* @return string
*/
public static function GetRealUserFriendlyName()
{
if (is_null(self::$m_oRealUser))
{
return '';
}
return self::$m_oRealUser->GetFriendlyName();
}
/**
* @return bool
*/
protected static function CheckLogin()
{
if (!self::IsLoggedIn())
{
//throw new UserRightException('No user logged in', array());
return false;
}
return true;
}
/**
* Add additional filter for organization silos to all the requests.
*
* @param string $sClass
* @param array $aSettings
*
* @return bool|\Expression
*/
public static function GetSelectFilter($sClass, $aSettings = array())
{
// When initializing, we need to let everything pass trough
if (!self::CheckLogin()) {return true;}
if (self::IsAdministrator()) {return true;}
try
{
// Check Bug 1436 for details
if (MetaModel::HasCategory($sClass, 'bizmodel') || MetaModel::HasCategory($sClass, 'silo'))
{
return self::$m_oAddOn->GetSelectFilter(self::$m_oUser, $sClass, $aSettings);
}
else
{
return true;
}
} catch (Exception $e)
{
return false;
}
}
/**
* @param string $sClass
* @param int $iActionCode
* @param \DBObjectSet $oInstanceSet
* @param \User $oUser
*
* @return int (UR_ALLOWED_YES|UR_ALLOWED_NO|UR_ALLOWED_DEPENDS)
* @throws \CoreException
*/
public static function IsActionAllowed($sClass, $iActionCode, $oInstanceSet = null, $oUser = null)
{
// When initializing, we need to let everything pass trough
if (!self::CheckLogin()) return UR_ALLOWED_YES;
if (MetaModel::DBIsReadOnly())
{
if ($iActionCode == UR_ACTION_CREATE) return UR_ALLOWED_NO;
if ($iActionCode == UR_ACTION_MODIFY) return UR_ALLOWED_NO;
if ($iActionCode == UR_ACTION_BULK_MODIFY) return UR_ALLOWED_NO;
if ($iActionCode == UR_ACTION_DELETE) return UR_ALLOWED_NO;
if ($iActionCode == UR_ACTION_BULK_DELETE) return UR_ALLOWED_NO;
}
$aPredefinedObjects = call_user_func(array($sClass, 'GetPredefinedObjects'));
if ($aPredefinedObjects != null)
{
// As opposed to the read-only DB, modifying an object is allowed
// (the constant columns will be marked as read-only)
//
if ($iActionCode == UR_ACTION_CREATE) return UR_ALLOWED_NO;
if ($iActionCode == UR_ACTION_DELETE) return UR_ALLOWED_NO;
if ($iActionCode == UR_ACTION_BULK_DELETE) return UR_ALLOWED_NO;
}
if (self::IsAdministrator($oUser)) return UR_ALLOWED_YES;
if (MetaModel::HasCategory($sClass, 'bizmodel') || MetaModel::HasCategory($sClass, 'grant_by_profile'))
{
if (is_null($oUser))
{
$oUser = self::$m_oUser;
}
if ($iActionCode == UR_ACTION_CREATE)
{
// The addons currently DO NOT handle the case "CREATE"
// Therefore it is considered to be equivalent to "MODIFY"
$iActionCode = UR_ACTION_MODIFY;
}
return self::$m_oAddOn->IsActionAllowed($oUser, $sClass, $iActionCode, $oInstanceSet);
}
elseif(($iActionCode == UR_ACTION_READ) && MetaModel::HasCategory($sClass, 'view_in_gui'))
{
return UR_ALLOWED_YES;
}
else
{
// Other classes could be edited/listed by the administrators
return UR_ALLOWED_NO;
}
}
/**
* @param string $sClass
* @param string $sStimulusCode
* @param \DBObjectSet|null $oInstanceSet
* @param \User|null $oUser
*
* @return bool
* @throws \CoreException
*/
public static function IsStimulusAllowed($sClass, $sStimulusCode, /*dbObjectSet*/ $oInstanceSet = null, $oUser = null)
{
// When initializing, we need to let everything pass trough
if (!self::CheckLogin()) return true;
if (MetaModel::DBIsReadOnly())
{
return false;
}
if (self::IsAdministrator($oUser)) return true;
if (MetaModel::HasCategory($sClass, 'bizmodel'))
{
if (is_null($oUser))
{
$oUser = self::$m_oUser;
}
return self::$m_oAddOn->IsStimulusAllowed($oUser, $sClass, $sStimulusCode, $oInstanceSet);
}
else
{
// Other classes could be edited/listed by the administrators
return false;
}
}
/**
* @param string $sClass
* @param string $sAttCode
* @param int $iActionCode
* @param \DBObjectSet $oInstanceSet
* @param \User $oUser
*
* @return int (UR_ALLOWED_YES|UR_ALLOWED_NO)
* @throws \CoreException
*/
public static function IsActionAllowedOnAttribute($sClass, $sAttCode, $iActionCode, /*dbObjectSet*/$oInstanceSet = null, $oUser = null)
{
// When initializing, we need to let everything pass trough
if (!self::CheckLogin()) return UR_ALLOWED_YES;
if (MetaModel::DBIsReadOnly())
{
if ($iActionCode == UR_ACTION_MODIFY) return UR_ALLOWED_NO;
if ($iActionCode == UR_ACTION_DELETE) return UR_ALLOWED_NO;
if ($iActionCode == UR_ACTION_BULK_MODIFY) return UR_ALLOWED_NO;
if ($iActionCode == UR_ACTION_BULK_DELETE) return UR_ALLOWED_NO;
}
if (self::IsAdministrator($oUser)) return UR_ALLOWED_YES;
if (MetaModel::HasCategory($sClass, 'bizmodel') || MetaModel::HasCategory($sClass, 'grant_by_profile'))
{
if (is_null($oUser))
{
$oUser = self::$m_oUser;
}
return self::$m_oAddOn->IsActionAllowedOnAttribute($oUser, $sClass, $sAttCode, $iActionCode, $oInstanceSet);
}
// this module is forbidden for non admins
if (MetaModel::HasCategory($sClass, 'addon/userrights')) return UR_ALLOWED_NO;
// the rest is allowed
return UR_ALLOWED_YES;
}
/**
* @param \User|null $oUser
*
* @return bool
*/
public static function IsAdministrator($oUser = null)
{
if (!self::CheckLogin()) return false;
if (is_null($oUser))
{
$oUser = self::$m_oUser;
}
$iUser = $oUser->GetKey();
if (!isset(self::$m_aAdmins[$iUser]))
{
self::$m_aAdmins[$iUser] = self::$m_oAddOn->IsAdministrator($oUser);
}
return self::$m_aAdmins[$iUser];
}
/**
* @param \User|null $oUser
*
* @return bool
*/
public static function IsPortalUser($oUser = null)
{
if (!self::CheckLogin()) return false;
if (is_null($oUser))
{
$oUser = self::$m_oUser;
}
$iUser = $oUser->GetKey();
if (!isset(self::$m_aPortalUsers[$iUser]))
{
self::$m_aPortalUsers[$iUser] = self::$m_oAddOn->IsPortalUser($oUser);
}
return self::$m_aPortalUsers[$iUser];
}
/**
* @return array
*/
public static function GetAllowedPortals()
{
$aAllowedPortals = array();
$aPortalsConf = PortalDispatcherData::GetData();
$aDispatchers = array();
foreach ($aPortalsConf as $sPortalId => $aConf)
{
$sHandlerClass = $aConf['handler'];
$aDispatchers[$sPortalId] = new $sHandlerClass($sPortalId);
}
foreach ($aDispatchers as $sPortalId => $oDispatcher)
{
if ($oDispatcher->IsUserAllowed())
{
$aAllowedPortals[] = array(
'id' => $sPortalId,
'label' => $oDispatcher->GetLabel(),
'url' => $oDispatcher->GetUrl(),
);
}
}
return $aAllowedPortals;
}
/**
* @see UR_ACTION_READ, UR_ACTION_MODIFY, ...
*
* @param int $iActionCode
* @param array $aCategories
* @param bool $bWithLabels
* @param \User|null $oUser
*
* @return array
* @throws \DictExceptionMissingString
* @throws \CoreException
*/
public static function GetAllowedClasses($iActionCode, $aCategories = array('bizmodel'), $bWithLabels = false, $oUser = null)
{
$aAllowedClasses = [];
foreach(MetaModel::GetClasses(implode(',', $aCategories)) as $sClass)
{
if(static::IsActionAllowed($sClass, $iActionCode, null, $oUser) === UR_ALLOWED_YES)
{
if($bWithLabels)
{
$aAllowedClasses[$sClass] = MetaModel::GetName($sClass);
}
else
{
$aAllowedClasses[] = $sClass;
}
}
}
// Sort by label
if($bWithLabels)
{
asort($aAllowedClasses);
}
return $aAllowedClasses;
}
/**
* @param \User|null $oUser
*
* @return array|mixed
*/
public static function ListProfiles($oUser = null)
{
if (is_null($oUser))
{
$oUser = self::$m_oUser;
}
if ($oUser === null)
{
// Not logged in: no profile at all
$aProfiles = array();
}
elseif ((self::$m_oUser !== null) && ($oUser->GetKey() == self::$m_oUser->GetKey()))
{
// Data about the current user can be found into the session data
if (array_key_exists('profile_list', $_SESSION))
{
$aProfiles = $_SESSION['profile_list'];
}
}
if (!isset($aProfiles))
{
$aProfiles = self::$m_oAddOn->ListProfiles($oUser);
}
return $aProfiles;
}
/**
* @param string $sProfileName Profile name to search for
* @param User|null $oUser
*
* @return bool
*/
public static function HasProfile($sProfileName, $oUser = null)
{
$bRet = in_array($sProfileName, self::ListProfiles($oUser));
return $bRet;
}
/**
* Reset cached data
* @param Bool Reset admin cache as well
* @return void
*/
public static function FlushPrivileges($bResetAdminCache = false)
{
if ($bResetAdminCache)
{
self::$m_aAdmins = array();
self::$m_aPortalUsers = array();
}
if (!isset($_SESSION) && !utils::IsModeCLI())
{
session_name('itop-'.md5(APPROOT));
session_start();
}
self::_ResetSessionCache();
if (self::$m_oAddOn)
{
self::$m_oAddOn->FlushPrivileges();
}
}
/**
* Find a user based on its login and its type of authentication
*
* @param string $sLogin Login/identifier of the user
* @param string $sAuthentication Type of authentication used: internal|external|any
* @param bool $bAllowDisabledUsers Whether or not to retrieve disabled users (status != enabled)
*
* @return User The found user or null
* @throws \OQLException
*/
protected static function FindUser($sLogin, $sAuthentication = 'any', $bAllowDisabledUsers = false)
{
if ($sAuthentication == 'any')
{
$oUser = self::FindUser($sLogin, 'internal');
if ($oUser == null)
{
$oUser = self::FindUser($sLogin, 'external');
}
}
else
{
if (!isset(self::$m_aCacheUsers))
{
self::$m_aCacheUsers = array('internal' => array(), 'external' => array());
}
if (!isset(self::$m_aCacheUsers[$sAuthentication][$sLogin]))
{
switch($sAuthentication)
{
case 'external':
$sBaseClass = 'UserExternal';
break;
case 'internal':
$sBaseClass = 'UserInternal';
break;
default:
echo "<p>sAuthentication = $sAuthentication</p>\n";
assert(false); // should never happen
}
$oSearch = DBObjectSearch::FromOQL("SELECT $sBaseClass WHERE login = :login");
if (!$bAllowDisabledUsers)
{
$oSearch->AddCondition('status', 'enabled');
}
$oSet = new DBObjectSet($oSearch, array(), array('login' => $sLogin));
$oUser = $oSet->fetch();
self::$m_aCacheUsers[$sAuthentication][$sLogin] = $oUser;
}
$oUser = self::$m_aCacheUsers[$sAuthentication][$sLogin];
}
return $oUser;
}
/**
* @param string$sClass
* @param array $aAllowedOrgs
* @param array $aSettings
* @param string|null $sAttCode
*
* @return \DBObjectSearch
* @throws \CoreException
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
*/
public static function MakeSelectFilter($sClass, $aAllowedOrgs, $aSettings = array(), $sAttCode = null)
{
return self::$m_oAddOn->MakeSelectFilter($sClass, $aAllowedOrgs, $aSettings, $sAttCode);
}
public static function _InitSessionCache()
{
// Cache data about the current user into the session
if (isset($_SESSION))
{
$_SESSION['profile_list'] = self::ListProfiles();
}
$oConfig = MetaModel::GetConfig();
$bSessionIdRegeneration = $oConfig->Get('regenerate_session_id_enabled');
if ($bSessionIdRegeneration)
{
// Protection against session fixation/injection: generate a new session id.
// Alas a PHP bug (technically a bug in the memcache session handler, https://bugs.php.net/bug.php?id=71187)
// causes session_regenerate_id to fail with a catchable fatal error in PHP 7.0 if the session handler is memcache(d).
// The bug has been fixed in PHP 7.2, but in case session_regenerate_id()
// fails we just silently ignore the error and keep the same session id...
$old_error_handler = set_error_handler(array(__CLASS__, 'VoidErrorHandler'));
session_regenerate_id();
if ($old_error_handler !== null)
{
set_error_handler($old_error_handler);
}
}
}
public static function _ResetSessionCache()
{
if (isset($_SESSION['profile_list']))
{
unset($_SESSION['profile_list']);
}
if (isset($_SESSION['archive_allowed']))
{
unset($_SESSION['archive_allowed']);
}
}
/**
* Fake error handler to silently discard fatal errors
* @param int $iErrNo
* @param string $sErrStr
* @param string $sErrFile
* @param int $iErrLine
* @return boolean
*/
public static function VoidErrorHandler($iErrno, $sErrStr, $sErrFile, $iErrLine)
{
return true; // Ignore the error
}
/**
* @return null|array The last login/result (null if none has failed) the array has this structure : array('sName' => $sName, 'bSuccess' => $bSuccess);
*/
public static function GetLastLoginStatus()
{
return self::$m_sLastLoginStatus;
}
}
/**
* Helper class to get the number/list of items for which a given action is allowed/possible
*/
class ActionChecker
{
var $oFilter;
var $iActionCode;
var $iAllowedCount = null;
var $aAllowedIDs = null;
public function __construct(DBSearch $oFilter, $iActionCode)
{
$this->oFilter = $oFilter;
$this->iActionCode = $iActionCode;
$this->iAllowedCount = null;
$this->aAllowedIDs = null;
}
/**
* returns the number of objects for which the action is allowed
* @return integer The number of "allowed" objects 0..N
*/
public function GetAllowedCount()
{
if ($this->iAllowedCount == null) $this->CheckObjects();
return $this->iAllowedCount;
}
/**
* If IsAllowed returned UR_ALLOWED_DEPENDS, this methods returns
* an array of ObjKey => Status (true|false)
* @return array
*/
public function GetAllowedIDs()
{
if ($this->aAllowedIDs == null) $this->IsAllowed();
return $this->aAllowedIDs;
}
/**
* Check if the speficied stimulus is allowed for the set of objects
* @return UR_ALLOWED_YES, UR_ALLOWED_NO or UR_ALLOWED_DEPENDS
*/
public function IsAllowed()
{
$sClass = $this->oFilter->GetClass();
$oSet = new DBObjectSet($this->oFilter);
$iActionAllowed = UserRights::IsActionAllowed($sClass, $this->iActionCode, $oSet);
if ($iActionAllowed == UR_ALLOWED_DEPENDS)
{
// Check for each object if the action is allowed or not
$this->aAllowedIDs = array();
$oSet->Rewind();
$this->iAllowedCount = 0;
while($oObj = $oSet->Fetch())
{
$oObjSet = DBObjectSet::FromArray($sClass, array($oObj));
if (UserRights::IsActionAllowed($sClass, $this->iActionCode, $oObjSet) == UR_ALLOWED_NO)
{
$this->aAllowedIDs[$oObj->GetKey()] = false;
}
else
{
// Assume UR_ALLOWED_YES, since there is just one object !
$this->aAllowedIDs[$oObj->GetKey()] = true;
$this->iAllowedCount++;
}
}
}
else if ($iActionAllowed == UR_ALLOWED_YES)
{
$this->iAllowedCount = $oSet->Count();
$this->aAllowedIDs = array(); // Optimization: not filled when Ok for all objects
}
else // UR_ALLOWED_NO
{
$this->iAllowedCount = 0;
$this->aAllowedIDs = array();
}
return $iActionAllowed;
}
}
/**
* Helper class to get the number/list of items for which a given stimulus can be applied (allowed & possible)
*/
class StimulusChecker extends ActionChecker
{
var $sState = null;
public function __construct(DBSearch $oFilter, $sState, $iStimulusCode)
{
parent::__construct($oFilter, $iStimulusCode);
$this->sState = $sState;
}
/**
* Check if the speficied stimulus is allowed for the set of objects
* @return UR_ALLOWED_YES, UR_ALLOWED_NO or UR_ALLOWED_DEPENDS
*/
public function IsAllowed()
{
$sClass = $this->oFilter->GetClass();
if (MetaModel::IsAbstract($sClass)) return UR_ALLOWED_NO; // Safeguard, not implemented if the base class of the set is abstract !
$oSet = new DBObjectSet($this->oFilter);
$iActionAllowed = UserRights::IsStimulusAllowed($sClass, $this->iActionCode, $oSet);
if ($iActionAllowed == UR_ALLOWED_NO)
{
$this->iAllowedCount = 0;
$this->aAllowedIDs = array();
}
else // Even if UR_ALLOWED_YES, we need to check if each object is in the appropriate state
{
// Hmmm, may not be needed right now because we limit the "multiple" action to object in
// the same state... may be useful later on if we want to extend this behavior...
// Check for each object if the action is allowed or not
$this->aAllowedIDs = array();
$oSet->Rewind();
$iAllowedCount = 0;
$iActionAllowed = UR_ALLOWED_DEPENDS;
while($oObj = $oSet->Fetch())
{
$aTransitions = $oObj->EnumTransitions();
if (array_key_exists($this->iActionCode, $aTransitions))
{
// Temporary optimization possible: since the current implementation
// of IsActionAllowed does not perform a 'per instance' check, we could
// skip this second validation phase and assume it would return UR_ALLOWED_YES
$oObjSet = DBObjectSet::FromArray($sClass, array($oObj));
if (!UserRights::IsStimulusAllowed($sClass, $this->iActionCode, $oObjSet))
{
$this->aAllowedIDs[$oObj->GetKey()] = false;
}
else
{
// Assume UR_ALLOWED_YES, since there is just one object !
$this->aAllowedIDs[$oObj->GetKey()] = true;
$this->iState = $oObj->GetState();
$this->iAllowedCount++;
}
}
else
{
$this->aAllowedIDs[$oObj->GetKey()] = false;
}
}
}
if ($this->iAllowedCount == $oSet->Count())
{
$iActionAllowed = UR_ALLOWED_YES;
}
if ($this->iAllowedCount == 0)
{
$iActionAllowed = UR_ALLOWED_NO;
}
return $iActionAllowed;
}
public function GetState()
{
return $this->iState;
}
}