/** * User rights management API * * @copyright Copyright (C) 2010-2012 Combodo SARL * @license http://opensource.org/licenses/AGPL-3.0 */ 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 { abstract public function CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage = 'EN US'); // could be used during initial installation abstract public function Init(); // loads data (possible optimizations) // Used to build select queries showing only objects visible for the given user abstract public function GetSelectFilter($sLogin, $sClass, $aSettings = array()); // returns a filter object abstract public function IsActionAllowed($oUser, $sClass, $iActionCode, /*dbObjectSet*/ $oInstanceSet = null); abstract public function IsStimulusAllowed($oUser, $sClass, $sStimulusCode, /*dbObjectSet*/ $oInstanceSet = null); abstract public function IsActionAllowedOnAttribute($oUser, $sClass, $sAttCode, $iActionCode, /*dbObjectSet*/ $oInstanceSet = null); abstract public function IsAdministrator($oUser); abstract public function IsPortalUser($oUser); abstract public function FlushPrivileges(); } abstract class User extends cmdbAbstractObject { public static function Init() { $aParams = array ( "category" => "core", "key_type" => "autoincrement", "name_attcode" => "login", "state_attcode" => "", "reconc_keys" => array(), "db_table" => "priv_user", "db_key_field" => "id", "db_finalclass_field" => "", "display_template" => "", ); 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 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 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', 'first_name', 'email', 'login', 'language', 'profile_list', 'allowed_org_list')); // Attributes to be displayed for the complete details MetaModel::Init_SetZListItems('list', array('finalclass', 'first_name', 'last_name', 'login')); // Attributes to be displayed for a list // Search criteria MetaModel::Init_SetZListItems('standard_search', array('login', 'contactid')); // Criteria of the std search form MetaModel::Init_SetZListItems('advanced_search', array('login', 'contactid')); // Criteria of the advanced search form } 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; } } } /* * Overload the standard behavior */ 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)) { $sNewLogin = $aChanges['login']; $oSearch = DBObjectSearch::FromOQL_AllData("SELECT User WHERE login = :newlogin"); $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 $oSet = $this->Get('profile_list'); if ($oSet->Count() == 0) { $this->m_aCheckIssues[] = Dict::Format('Class:User/Error:AtLeastOneProfileIsNeeded'); } } function GetGrantAsHtml($sClass, $iAction) { if (UserRights::IsActionAllowed($sClass, $iAction, null, $this)) { return ''.Dict::S('UI:UserManagement:ActionAllowed:Yes').''; } else { return ''.Dict::S('UI:UserManagement:ActionAllowed:No').''; } } 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[] = ''.htmlentities($oStimulus->GetLabel(), ENT_QUOTES, 'UTF-8').''; } } $sStimuli = implode(', ', $aStimuli); } else { $sStimuli = ''.Dict::S('UI:UserManagement:NoLifeCycleApplicable').''; } $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), '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['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, 'bizmodel'); // debug if (false) { $oPage->SetCurrentTab('More on user rigths (dev only)'); $oPage->add("
sAuthentication = $sAuthentication
\n"; assert(false); // should never happen } $oSearch = DBObjectSearch::FromOQL("SELECT $sBaseClass WHERE login = :login"); $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; } } /** * 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(DBObjectSearch $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(DBObjectSearch $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; } } /** * Self-register extension to allow the automatic creation & update of CAS users * * @package iTopORM * */ class CAS_SelfRegister implements 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 CAS authenticated user name * @param string $sPassword Ignored * @param string $sLoginMode The login mode used (cas|form|basic|url) * @param string $sAuthentication The authentication method used * @return bool true if the user is a valid one, false otherwise */ public static function CheckCredentialsAndCreateUser($sName, $sPassword, $sLoginMode, $sAuthentication) { $bOk = true; if ($sLoginMode != 'cas') return false; // Must be authenticated via CAS $sCASMemberships = MetaModel::GetConfig()->Get('cas_memberof'); $bFound = false; if (!empty($sCASMemberships)) { if (phpCAS::hasAttribute('memberOf')) { // A list of groups is specified, the user must a be member of (at least) one of them to pass $aCASMemberships = array(); $aTmp = explode(';', $sCASMemberships); setlocale(LC_ALL, "en_US.utf8"); // !!! WARNING: this is needed to have the iconv //TRANSLIT working fine below !!! foreach($aTmp as $sGroupName) { $aCASMemberships[] = trim(iconv('UTF-8', 'ASCII//TRANSLIT', $sGroupName)); // Just in case remove accents and spaces... } $aMemberOf = phpCAS::getAttribute('memberOf'); if (!is_array($aMemberOf)) $aMemberOf = array($aMemberOf); // Just one entry, turn it into an array $aFilteredGroupNames = array(); foreach($aMemberOf as $sGroupName) { phpCAS::log("Info: user if a member of the group: ".$sGroupName); $sGroupName = trim(iconv('UTF-8', 'ASCII//TRANSLIT', $sGroupName)); // Remove accents and spaces as well $aFilteredGroupNames[] = $sGroupName; $bIsMember = false; foreach($aCASMemberships as $sCASPattern) { if (self::IsPattern($sCASPattern)) { if (preg_match($sCASPattern, $sGroupName)) { $bIsMember = true; break; } } else if ($sPattern == $sGroupName) { $bIsMember = true; break; } } if ($bIsMember) { $bCASUserSynchro = MetaModel::GetConfig()->Get('cas_user_synchro'); if ($bCASUserSynchro) { // If needed create a new user for this email/profile phpCAS::log('Info: cas_user_synchro is ON'); $bOk = self::CreateCASUser(phpCAS::getUser(), $aMemberOf); if($bOk) { $bFound = true; } else { phpCAS::log("User ".phpCAS::getUser()." cannot be created in iTop. Logging off..."); } } else { phpCAS::log('Info: cas_user_synchro is OFF'); $bFound = true; } break; } } if($bOk && !$bFound) { phpCAS::log("User ".phpCAS::getUser().", none of his/her groups (".implode('; ', $aFilteredGroupNames).") match any of the required groups: ".implode('; ', $aCASMemberships)); } } else { // Too bad, the user is not part of any of the group => not allowed phpCAS::log("No 'memberOf' attribute found for user ".phpCAS::getUser().". Are you using the SAML protocol (S1) ?"); } } else { // No membership required, anybody will pass $bFound = true; } if (!$bFound) { // The user is not part of the allowed groups, => log out $sUrl = utils::GetAbsoluteUrlAppRoot().'pages/UI.php'; $sCASLogoutUrl = MetaModel::GetConfig()->Get('cas_logout_redirect_service'); if (empty($sCASLogoutUrl)) { $sCASLogoutUrl = $sUrl; } phpCAS::logoutWithRedirectService($sCASLogoutUrl); // Redirects to the CAS logout page // Will never return ! } return $bFound; } /** * Called after the user has been authenticated and found in iTop. This method can * Update the user's definition (profiles...) on the fly 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) { $bCASUpdateProfiles = MetaModel::GetConfig()->Get('cas_update_profiles'); if (($sLoginMode == 'cas') && $bCASUpdateProfiles && (phpCAS::hasAttribute('memberOf'))) { $aMemberOf = phpCAS::getAttribute('memberOf'); if (!is_array($aMemberOf)) $aMemberOf = array($aMemberOf); // Just one entry, turn it into an array return self::SetProfilesFromCAS($oUser, $aMemberOf); } // No groups defined in CAS or not CAS at all: do nothing... return true; } /** * Helper method to create a CAS based user * @param string $sEmail * @param array $aGroups * @return bool true on success, false otherwise */ protected static function CreateCASUser($sEmail, $aGroups) { if (!MetaModel::IsValidClass('URP_Profiles')) { phpCAS::log("URP_Profiles is not a valid class. Automatic creation of Users is not supported in this context, sorry."); return false; } $oUser = MetaModel::GetObjectByName('UserExternal', $sEmail, false); if ($oUser == null) { // Create the user, link it to a contact phpCAS::log("Info: the user '$sEmail' does not exist. A new UserExternal will be created."); $oSearch = new DBObjectSearch('Person'); $oSearch->AddCondition('email', $sEmail); $oSet = new DBObjectSet($oSearch); $iContactId = 0; switch($oSet->Count()) { case 0: phpCAS::log("Error: found no contact with the email: '$sEmail'. Cannot create the user in iTop."); return false; case 1: $oContact = $oSet->Fetch(); $iContactId = $oContact->GetKey(); phpCAS::log("Info: Found 1 contact '".$oContact->GetName()."' (id=$iContactId) corresponding to the email '$sEmail'."); break; default: phpCAS::log("Error: ".$oSet->Count()." contacts have the same email: '$sEmail'. Cannot create a user for this email."); return false; } $oUser = new UserExternal(); $oUser->Set('login', $sEmail); $oUser->Set('contactid', $iContactId); $oUser->Set('language', MetaModel::GetConfig()->GetDefaultLanguage()); } else { phpCAS::log("Info: the user '$sEmail' already exists (id=".$oUser->GetKey().")."); } // Now synchronize the profiles if (!self::SetProfilesFromCAS($oUser, $aGroups)) { return false; } else { if ($oUser->IsNew() || $oUser->IsModified()) { $oMyChange = MetaModel::NewObject("CMDBChange"); $oMyChange->Set("date", time()); $oMyChange->Set("userinfo", 'CAS/LDAP Synchro'); $oMyChange->DBInsert(); if ($oUser->IsNew()) { $oUser->DBInsertTracked($oMyChange); } else { $oUser->DBUpdateTracked($oMyChange); } } return true; } } protected static function SetProfilesFromCAS($oUser, $aGroups) { if (!MetaModel::IsValidClass('URP_Profiles')) { phpCAS::log("URP_Profiles is not a valid class. Automatic creation of Users is not supported in this context, sorry."); return false; } // read all the existing profiles $oProfilesSearch = new DBObjectSearch('URP_Profiles'); $oProfilesSet = new DBObjectSet($oProfilesSearch); $aAllProfiles = array(); while($oProfile = $oProfilesSet->Fetch()) { $aAllProfiles[strtolower($oProfile->GetName())] = $oProfile->GetKey(); } // Translate the CAS/LDAP group names into iTop profile names $aProfiles = array(); $sPattern = MetaModel::GetConfig()->Get('cas_profile_pattern'); foreach($aGroups as $sGroupName) { if (preg_match($sPattern, $sGroupName, $aMatches)) { if (array_key_exists(strtolower($aMatches[1]), $aAllProfiles)) { $aProfiles[] = $aAllProfiles[strtolower($aMatches[1])]; phpCAS::log("Info: Adding the profile '{$aMatches[1]}' from CAS."); } else { phpCAS::log("Warning: {$aMatches[1]} is not a valid iTop profile (extracted from group name: '$sGroupName'). Ignored."); } } else { phpCAS::log("Info: The CAS group '$sGroupName' does not seem to match an iTop pattern. Ignored."); } } if (count($aProfiles) == 0) { phpCAS::log("Info: The user '".$oUser->GetName()."' has no profiles retrieved from CAS. Default profile(s) will be used."); // Second attempt: check if there is/are valid default profile(s) $sCASDefaultProfiles = MetaModel::GetConfig()->Get('cas_default_profiles'); $aCASDefaultProfiles = explode(';', $sCASDefaultProfiles); foreach($aCASDefaultProfiles as $sDefaultProfileName) { if (array_key_exists(strtolower($sDefaultProfileName), $aAllProfiles)) { $aProfiles[] = $aAllProfiles[strtolower($sDefaultProfileName)]; phpCAS::log("Info: Adding the default profile '".$aAllProfiles[strtolower($sDefaultProfileName)]."' from CAS."); } else { phpCAS::log("Warning: the default profile {$sDefaultProfileName} is not a valid iTop profile. Ignored."); } } if (count($aProfiles) == 0) { phpCAS::log("Error: The user '".$oUser->GetName()."' has no profiles in iTop, and therefore cannot be created."); return false; } } // Now synchronize the profiles $oProfilesSet = DBObjectSet::FromScratch('URP_UserProfile'); foreach($aProfiles as $iProfileId) { $oLink = new URP_UserProfile(); $oLink->Set('profileid', $iProfileId); $oLink->Set('reason', 'CAS/LDAP Synchro'); $oProfilesSet->AddObject($oLink); } $oUser->Set('profile_list', $oProfilesSet); phpCAS::log("Info: the user '".$oUser->GetName()."' (id=".$oUser->GetKey().") now has the following profiles: '".implode("', '", $aProfiles)."'."); if ($oUser->IsModified()) { $oMyChange = MetaModel::NewObject("CMDBChange"); $oMyChange->Set("date", time()); $oMyChange->Set("userinfo", 'CAS/LDAP Synchro'); $oMyChange->DBInsert(); if ($oUser->IsNew()) { $oUser->DBInsertTracked($oMyChange); } else { $oUser->DBUpdateTracked($oMyChange); } } return true; } /** * Helper function to check if the supplied string is a litteral string or a regular expression pattern * @param string $sCASPattern * @return bool True if it's a regular expression pattern, false otherwise */ protected static function IsPattern($sCASPattern) { if ((substr($sCASPattern, 0, 1) == '/') && (substr($sCASPattern, -1) == '/')) { // the string is enclosed by slashes, let's assume it's a pattern return true; } else { return false; } } } // By default enable the 'CAS_SelfRegister' defined above UserRights::SelectSelfRegister('CAS_SelfRegister'); ?>