From 88efd3752562d24560e10508a1674ab771155dbc Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Fri, 27 Aug 2010 14:18:58 +0000 Subject: [PATCH] SVN:trunk[708] --- application/cmdbabstract.class.inc.php | 13 +- application/menunode.class.inc.php | 159 ++++++++++-- application/template.class.inc.php | 2 +- application/ui.passwordwidget.class.inc.php | 68 +++++ core/attributedef.class.inc.php | 199 ++++++++++++++- core/cmdbchangeop.class.inc.php | 120 ++++++++- core/cmdbobject.class.inc.php | 42 +++- core/config.class.inc.php | 20 ++ core/metamodel.class.php | 5 + core/ormpassword.class.inc.php | 119 +++++++++ core/simplecrypt.class.inc.php | 235 ++++++++++++++++++ dictionaries/dictionary.itop.core.php | 9 + dictionaries/dictionary.itop.ui.php | 8 +- dictionaries/es_cr.dictionary.itop.core.php | 7 + dictionaries/es_cr.dictionary.itop.ui.php | 5 +- dictionaries/fr.dictionary.itop.core.php | 55 ++-- dictionaries/fr.dictionary.itop.ui.php | 3 + js/forms-json-utils.js | 42 ++++ modules/authent-local/model.authent-local.php | 12 +- .../en.dict.itop-change-mgmt.php | 6 + .../es_cr.dict.itop-change-mgmt.php | 6 + .../fr.dict.itop-change-mgmt.php | 6 + .../model.itop-change-mgmt.php | 11 +- .../fr.dict.itop-config-mgmt.php | 5 +- .../model.itop-config-mgmt.php | 22 +- .../en.dict.itop-incident-mgmt.php | 6 + .../es_cr.dict.itop-incident-mgmt.php | 6 + .../fr.dict.itop-incident-mgmt.php | 6 + .../model.itop-incident-mgmt.php | 9 +- .../en.dict.itop-request-mgmt.php | 6 + .../es_cr.dict.itop-request-mgmt.php | 6 + .../fr.dict.itop-request-mgmt.php | 6 + .../model.itop-request-mgmt.php | 9 +- pages/UI.php | 15 +- 34 files changed, 1161 insertions(+), 87 deletions(-) create mode 100644 application/ui.passwordwidget.class.inc.php create mode 100644 core/ormpassword.class.inc.php create mode 100644 core/simplecrypt.class.inc.php diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 37a6908da..d539c403d 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -35,6 +35,7 @@ require_once('../core/cmdbobject.class.inc.php'); require_once('../application/utils.inc.php'); require_once('../application/applicationcontext.class.inc.php'); require_once('../application/ui.linkswidget.class.inc.php'); +require_once('../application/ui.passwordwidget.class.inc.php'); abstract class cmdbAbstractObject extends CMDBObject { @@ -220,6 +221,7 @@ abstract class cmdbAbstractObject extends CMDBObject 'target_attr' => $oAttDef->GetExtKeyToRemote(), 'view_link' => false, 'menu' => false, + 'display_limit' => true, // By default limit the list to speed up the initial load & display ); } $oPage->p(MetaModel::GetClassIcon($sTargetClass)." ".$oAttDef->GetDescription()); @@ -1001,6 +1003,12 @@ abstract class cmdbAbstractObject extends CMDBObject $sHTMLValue = ''; break; + case 'One Way Password': + $oWidget = new UIPasswordWidget($sAttCode, $iId, $sNameSuffix); + $sHTMLValue = $oWidget->Display($oPage, $aArgs); + // Event list & validation is handled directly by the widget + break; + case 'String': default: // #@# todo - add context information (depending on dimensions) @@ -1056,7 +1064,10 @@ abstract class cmdbAbstractObject extends CMDBObject break; } $sPattern = addslashes($oAttDef->GetValidationPattern()); //'^([0-9]+)$'; - $oPage->add_ready_script("$('#$iId').bind('".implode(' ', $aEventsList)."', function(evt, sFormId) { return ValidateField('$iId', '$sPattern', $bMandatory, sFormId) } );"); // Bind to a custom event: validate + if (!empty($aEventlist)) + { + $oPage->add_ready_script("$('#$iId').bind('".implode(' ', $aEventsList)."', function(evt, sFormId) { return ValidateField('$iId', '$sPattern', $bMandatory, sFormId) } );"); // Bind to a custom event: validate + } $aDependencies = MetaModel::GetDependentAttributes($sClass, $sAttCode); // List of attributes that depend on the current one if (count($aDependencies) > 0) { diff --git a/application/menunode.class.inc.php b/application/menunode.class.inc.php index e511e85a5..1d66369f4 100644 --- a/application/menunode.class.inc.php +++ b/application/menunode.class.inc.php @@ -131,27 +131,30 @@ class ApplicationMenu { $index = $aMenu['index']; $oMenu = self::GetMenuNode($index); - $aChildren = self::GetChildren($index); - $sCSSClass = (count($aChildren) > 0) ? ' class="submenu"' : ''; - $sHyperlink = $oMenu->GetHyperlink($aExtraParams); - if ($sHyperlink != '') + if ($oMenu->IsEnabled()) { - $oPage->AddToMenu(''.$oMenu->GetTitle().''); - } - else - { - $oPage->AddToMenu(''.$oMenu->GetTitle().''); - } - $aCurrentMenu = self::$aMenusIndex[$index]; - if ($iActiveMenu == $index) - { - $bActive = true; - } - if (count($aChildren) > 0) - { - $oPage->AddToMenu('
    '); - $bActive |= self::DisplaySubMenu($oPage, $aChildren, $aExtraParams, $iActiveMenu); - $oPage->AddToMenu('
'); + $aChildren = self::GetChildren($index); + $sCSSClass = (count($aChildren) > 0) ? ' class="submenu"' : ''; + $sHyperlink = $oMenu->GetHyperlink($aExtraParams); + if ($sHyperlink != '') + { + $oPage->AddToMenu(''.$oMenu->GetTitle().''); + } + else + { + $oPage->AddToMenu(''.$oMenu->GetTitle().''); + } + $aCurrentMenu = self::$aMenusIndex[$index]; + if ($iActiveMenu == $index) + { + $bActive = true; + } + if (count($aChildren) > 0) + { + $oPage->AddToMenu('
    '); + $bActive |= self::DisplaySubMenu($oPage, $aChildren, $aExtraParams, $iActiveMenu); + $oPage->AddToMenu('
'); + } } } return $bActive; @@ -298,6 +301,15 @@ abstract class MenuNode return $this->AddParams('../pages/UI.php', $aExtraParams); } + /** + * Tells whether the menu is enabled (i.e. displayed) for the current user + * @return bool True if enabled, false otherwise + */ + public function IsEnabled() + { + return true; + } + public abstract function RenderContent(WebPage $oPage, $aExtraParams = array()); protected function AddParams($sHyperlink, $aExtraParams) @@ -394,20 +406,23 @@ class OQLMenuNode extends MenuNode { protected $sPageTitle; protected $sOQL; + protected $bSearch; /** * Create a menu item based on an OQL query and inserts it into the application's main menu * @param string $sMenuId Unique identifier of the menu (used to identify the menu for bookmarking, and for getting the labels from the dictionary) - * @param string $sPageTitle Title displayed into the page's content (will be looked-up in the dictionnary for translation) + * @param string $sOQL OQL query defining the set of objects to be displayed * @param integer $iParentIndex ID of the parent menu * @param float $fRank Number used to order the list, any number will do, but for a given level (i.e same parent) all menus are sorted based on this value + * @param bool $bSearch Whether or not to display a (collapsed) search frame at the top of the page * @return MenuNode */ - public function __construct($sMenuId, $sOQL, $iParentIndex, $fRank = 0) + public function __construct($sMenuId, $sOQL, $iParentIndex, $fRank = 0, $bSearch = false) { parent::__construct($sMenuId, $iParentIndex, $fRank); $this->sPageTitle = "Menu:$sMenuId+"; $this->sOQL = $sOQL; + $this->bSearch = $bSearch; } public function RenderContent(WebPage $oPage, $aExtraParams = array()) @@ -422,8 +437,16 @@ class OQLMenuNode extends MenuNode $sIcon = ''; } // The standard template used for all such pages: a (closed) search form at the top and a list of results at the bottom - $sTemplate = <<bSearch) + { + $sTemplate .= <<$this->sOQL +EOF; + } + + $sTemplate .= <<$sIcon$this->sPageTitle

$this->sOQL EOF; @@ -431,6 +454,40 @@ EOF; $oTemplate->Render($oPage, $aExtraParams); } } +/** + * This class defines a menu item that displays a search form for the given class of objects + */ +class SearchMenuNode extends MenuNode +{ + protected $sPageTitle; + protected $sClass; + + /** + * Create a menu item based on an OQL query and inserts it into the application's main menu + * @param string $sMenuId Unique identifier of the menu (used to identify the menu for bookmarking, and for getting the labels from the dictionary) + * @param string $sClass The class of objects to search for + * @param string $sPageTitle Title displayed into the page's content (will be looked-up in the dictionnary for translation) + * @param integer $iParentIndex ID of the parent menu + * @param float $fRank Number used to order the list, any number will do, but for a given level (i.e same parent) all menus are sorted based on this value + * @return MenuNode + */ + public function __construct($sMenuId, $sClass, $iParentIndex, $fRank = 0) + { + parent::__construct($sMenuId, $iParentIndex, $fRank); + $this->sPageTitle = "Menu:$sMenuId+"; + $this->sClass = $sClass; + } + + public function RenderContent(WebPage $oPage, $aExtraParams = array()) + { + // The standard template used for all such pages: an open search form at the top + $sTemplate = <<SELECT $this->sClass +EOF; + $oTemplate = new DisplayTemplate($sTemplate); + $oTemplate->Render($oPage, $aExtraParams); + } +} /** * This class defines a menu that points to any web page. It takes only two parameters: @@ -468,4 +525,60 @@ class WebPageMenuNode extends MenuNode assert(false); // Shall never be called, the external web page will handle the display by itself } } + +/** + * This class defines a menu that points to the page for creating a new object of the specified class. + * It take only one parameter: the name of the class + * Note: the parameter menu=xxx (where xxx is the id of the menu itself) will be added to the hyperlink + * in order to make it the active one + */ +class NewObjectMenuNode extends MenuNode +{ + protected $sClass; + + /** + * Create a menu item that points to the URL for creating a new object, the menu will be added only if the current user has enough + * rights to create such an object (or an object of a child class) + * @param string $sMenuId Unique identifier of the menu (used to identify the menu for bookmarking, and for getting the labels from the dictionary) + * @param string $sClass URL to the page to load. Use relative URL if you want to keep the application portable ! + * @param integer $iParentIndex ID of the parent menu + * @param float $fRank Number used to order the list, any number will do, but for a given level (i.e same parent) all menus are sorted based on this value + * @return MenuNode + */ + public function __construct($sMenuId, $sClass, $iParentIndex, $fRank = 0) + { + parent::__construct($sMenuId, $iParentIndex, $fRank); + $this->sClass = $sClass; + } + + public function GetHyperlink($aExtraParams) + { + $sHyperlink = '../pages/UI.php?operation=new&class='.$this->sClass; + $aExtraParams['menu'] = $this->GetIndex(); + return $this->AddParams($sHyperlink, $aExtraParams); + } + + public function IsEnabled() + { + // Enable this menu, only if the current user has enough rights to create such an object, or an object of + // any child class + + $aSubClasses = MetaModel::EnumChildClasses($this->sClass, ENUM_CHILD_CLASSES_ALL); // Including the specified class itself + $bActionIsAllowed = false; + + foreach($aSubClasses as $sCandidateClass) + { + if (!MetaModel::IsAbstract($sCandidateClass) && (UserRights::IsActionAllowed($sCandidateClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES)) + { + $bActionIsAllowed = true; + break; // Enough for now + } + } + return $bActionIsAllowed; + } + public function RenderContent(WebPage $oPage, $aExtraParams = array()) + { + assert(false); // Shall never be called, the external web page will handle the display by itself + } +} ?> diff --git a/application/template.class.inc.php b/application/template.class.inc.php index 964dc40e3..3fb8ad094 100644 --- a/application/template.class.inc.php +++ b/application/template.class.inc.php @@ -170,7 +170,7 @@ class DisplayTemplate case 'itopcheck': $sClassName = $aAttributes['class']; - if (MetaModel::IsValidClass($sClassName)) + if (MetaModel::IsValidClass($sClassName) && UserRights::IsActionAllowed($sClassName, UR_ACTION_READ)) { $oTemplate = new DisplayTemplate($sContent); $oTemplate->Render($oPage, array()); // no params to apply, they have already been applied diff --git a/application/ui.passwordwidget.class.inc.php b/application/ui.passwordwidget.class.inc.php new file mode 100644 index 000000000..43f240ba4 --- /dev/null +++ b/application/ui.passwordwidget.class.inc.php @@ -0,0 +1,68 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +require_once('../application/webpage.class.inc.php'); +require_once('../application/displayblock.class.inc.php'); + +class UIPasswordWidget +{ + protected static $iWidgetIndex = 0; + protected $sAttCode; + protected $sNameSuffix; + protected $iId; + + public function __construct($sAttCode, $iInputId, $sNameSuffix = '') + { + self::$iWidgetIndex++; + $this->sAttCode = $sAttCode; + $this->sNameSuffix = $sNameSuffix; + $this->iId = $iInputId; + } + + /** + * Get the HTML fragment corresponding to the linkset editing widget + * @param WebPage $oP The web page used for all the output + * @param Hash $aArgs Extra context arguments + * @return string The HTML fragment to be inserted into the page + */ + public function Display(WebPage $oPage, $aArgs = array()) + { + $sCode = $this->sAttCode.$this->sNameSuffix; + $iWidgetIndex = self::$iWidgetIndex; + $sHtmlValue = ''; + $sHtmlValue = ' 
'; + $sHtmlValue .= ' '.Dict::S('UI:PasswordConfirm').' '; + $sHtmlValue .= ''; + + $oPage->add_ready_script("$('#$this->iId').bind('keyup change', function(evt) { return PasswordFieldChanged('$this->iId') } );"); // Bind to a custom event: validate + $oPage->add_ready_script("$('#$this->iId').bind('keyup change', function(evt) { return PasswordFieldChanged('$this->iId') } );"); // Bind to a custom event: validate + $oPage->add_ready_script("$('#$this->iId').bind('keyup change validate', function(evt, sFormId) { return ValidatePasswordField('$this->iId', sFormId) } );"); // Bind to a custom event: validate + $oPage->add_ready_script("$('#{$this->iId}_confirm').bind('keyup change', function(evt, sFormId) { return ValidatePasswordField('$this->iId', sFormId) } );"); // Bind to a custom event: validate + + return $sHtmlValue; + } +} +?> \ No newline at end of file diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index d541eeef6..895ffec48 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -26,6 +26,7 @@ require_once('MyHelpers.class.inc.php'); require_once('ormdocument.class.inc.php'); +require_once('ormpassword.class.inc.php'); /** * MissingColumnException - sent if an attribute is being created but the column is missing in the row @@ -377,7 +378,7 @@ class AttributeLinkedSetIndirect extends AttributeLinkedSet * @package iTopORM */ class AttributeDBFieldVoid extends AttributeDefinition -{ +{ static protected function ListExpectedParams() { return array_merge(parent::ListExpectedParams(), array("allowed_values", "depends_on", "sql")); @@ -465,7 +466,7 @@ class AttributeDBFieldVoid extends AttributeDefinition * @package iTopORM */ class AttributeDBField extends AttributeDBFieldVoid -{ +{ static protected function ListExpectedParams() { return array_merge(parent::ListExpectedParams(), array("default_value", "is_null_allowed")); @@ -861,6 +862,66 @@ class AttributePassword extends AttributeString } } +/** + * Map a text column (size < 255) to an attribute that is encrypted in the database + * The encryption is based on a key set per iTop instance. Thus if you export your + * database (in SQL) to someone else without providing the key at the same time + * the encrypted fields will remain encrypted + * + * @package iTopORM + */ +class AttributeEncryptedString extends AttributeString +{ + static $sKey = null; // Encryption key used for all encrypted fields + + public function __construct($sCode, $aParams) + { + parent::__construct($sCode, $aParams); + if (self::$sKey == null) + { + self::$sKey = MetaModel::GetConfig()->GetEncryptionKey(); + } + } + + protected function GetSQLCol() {return "TINYBLOB";} + + public function GetFilterDefinitions() + { + // Note: due to this, you will get an error if a an encrypted field is declared as a search criteria (see ZLists) + // not allowed to search on encrypted fields ! + return array(); + } + + public function MakeRealValue($proposedValue) + { + if (is_null($proposedValue)) return null; + return (string)$proposedValue; + } + + /** + * Decrypt the value when reading from the database + */ + public function FromSQLToValue($aCols, $sPrefix = '') + { + $oSimpleCrypt = new SimpleCrypt(); + $sValue = $oSimpleCrypt->Decrypt(self::$sKey, $aCols[$sPrefix]); + return $sValue; + } + + /** + * Encrypt the value before storing it in the database + */ + public function GetSQLValues($value) + { + $oSimpleCrypt = new SimpleCrypt(); + $encryptedValue = $oSimpleCrypt->Encrypt(self::$sKey, $value); + + $aValues = array(); + $aValues[$this->Get("sql")] = $encryptedValue; + return $aValues; + } +} + /** * Map a text column (size > ?) to an attribute * @@ -1880,6 +1941,140 @@ class AttributeBlob extends AttributeDefinition return ''; // Not exportable in XML, or as CDATA + some subtags ?? } } +/** + * One way encrypted (hashed) password + */ +class AttributeOneWayPassword extends AttributeDefinition +{ + static protected function ListExpectedParams() + { + return array_merge(parent::ListExpectedParams(), array("depends_on")); + } + + public function GetType() {return "One Way Password";} + public function GetTypeDesc() {return "One Way Password";} + public function GetEditClass() {return "One Way Password";} + + public function IsDirectField() {return true;} + public function IsScalar() {return true;} + public function IsWritable() {return true;} + public function GetDefaultValue() {return "";} + public function IsNullAllowed() {return $this->GetOptional("is_null_allowed", false);} + + // Facilitate things: allow the user to Set the value from a string or from an ormPassword (already encrypted) + public function MakeRealValue($proposedValue) + { + $oPassword = $proposedValue; + if (!is_object($oPassword)) + { + $oPassword = new ormPassword('', ''); + $oPassword->SetPassword($proposedValue); + } + return $oPassword; + } + + public function GetSQLExpressions() + { + $aColumns = array(); + // Note: to optimize things, the existence of the attribute is determined by the existence of one column with an empty suffix + $aColumns[''] = $this->GetCode().'_hash'; + $aColumns['_salt'] = $this->GetCode().'_salt'; + return $aColumns; + } + + public function FromSQLToValue($aCols, $sPrefix = '') + { + if (!isset($aCols[$sPrefix])) + { + $sAvailable = implode(', ', array_keys($aCols)); + throw new MissingColumnException("Missing column '$sPrefix' from {$sAvailable}"); + } + $hashed = $aCols[$sPrefix]; + + if (!isset($aCols[$sPrefix.'_salt'])) + { + $sAvailable = implode(', ', array_keys($aCols)); + throw new MissingColumnException("Missing column '".$sPrefix."_salt' from {$sAvailable}"); + } + $sSalt = $aCols[$sPrefix.'_salt']; + + $value = new ormPassword($hashed, $sSalt); + return $value; + } + + public function GetSQLValues($value) + { + // #@# Optimization: do not load blobs anytime + // As per mySQL doc, selecting blob columns will prevent mySQL from + // using memory in case a temporary table has to be created + // (temporary tables created on disk) + // We will have to remove the blobs from the list of attributes when doing the select + // then the use of Get() should finalize the load + if ($value instanceOf ormPassword) + { + $aValues = array(); + $aValues[$this->GetCode().'_hash'] = $value->GetHash(); + $aValues[$this->GetCode().'_salt'] = $value->GetSalt(); + } + else + { + $aValues = array(); + $aValues[$this->GetCode().'_hash'] = ''; + $aValues[$this->GetCode().'_salt'] = ''; + echo "Writing an empty password !!!"; + echo "
\n";
+			print_r($value);
+			echo "
\n"; + } + return $aValues; + } + + public function GetSQLColumns() + { + $aColumns = array(); + $aColumns[$this->GetCode().'_hash'] = 'TINYBLOB'; + $aColumns[$this->GetCode().'_salt'] = 'TINYBLOB'; + return $aColumns; + } + + public function GetFilterDefinitions() + { + return array(); + // still not working... see later... + } + + public function GetBasicFilterOperators() + { + return array(); + } + public function GetBasicFilterLooseOperator() + { + return '='; + } + + public function GetBasicFilterSQLExpr($sOpCode, $value) + { + return 'true'; + } + + public function GetAsHTML($value) + { + if (is_object($value)) + { + return $value->GetAsHTML(); + } + } + + public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"') + { + return ''; // Not exportable in CSV + } + + public function GetAsXML($value) + { + return ''; // Not exportable in XML + } +} // Indexed array having two dimensions class AttributeTable extends AttributeText diff --git a/core/cmdbchangeop.class.inc.php b/core/cmdbchangeop.class.inc.php index ab520fe92..f32dc2bf3 100644 --- a/core/cmdbchangeop.class.inc.php +++ b/core/cmdbchangeop.class.inc.php @@ -102,7 +102,7 @@ class CMDBChangeOpCreate extends CMDBChangeOp */ public function GetDescription() { - return 'Object created'; + return Dict::S('Change:ObjectCreated'); } } @@ -135,7 +135,7 @@ class CMDBChangeOpDelete extends CMDBChangeOp */ public function GetDescription() { - return 'Object deleted'; + return Dict::S('Change:ObjectDeleted'); } } @@ -228,16 +228,16 @@ class CMDBChangeOpSetAttributeScalar extends CMDBChangeOpSetAttribute if (substr($sNewValue,0, strlen($sOldValue)) == $sOldValue) // Text added at the end { $sDelta = substr($sNewValue, strlen($sOldValue)); - $sResult = "$sDelta appended to $sAttName"; + $sResult = Dict::Format('Change:Text_AppendedTo_AttName', $sDelta, $sAttName); } else if (substr($sNewValue, -strlen($sOldValue)) == $sOldValue) // Text added at the beginning { $sDelta = substr($sNewValue, 0, strlen($sNewValue) - strlen($sOldValue)); - $sResult = "$sDelta appended to $sAttName"; + $sResult = Dict::Format('Change:Text_AppendedTo_AttName', $sDelta, $sAttName); } else { - $sResult = "$sAttName set to $sNewValue (previous value: $sOldValue)"; + $sResult = Dict::Format('Change:AttName_SetTo_NewValue_PreviousValue_OldValue', $sAttName, $sNewValue, $sOldValue); } } elseif($bIsHtml && $oAttDef->IsExternalKey()) @@ -253,7 +253,7 @@ class CMDBChangeOpSetAttributeScalar extends CMDBChangeOpSetAttribute } else { - $sResult = "$sAttName set to $sNewValue (previous value: $sOldValue)"; + $sResult = Dict::Format('Change:Att_SetTo_NewValue_PreviousValue_OldValue', $sAttName, $sNewValue, $sOldValue); } } return $sResult; @@ -313,7 +313,111 @@ class CMDBChangeOpSetAttributeBlob extends CMDBChangeOpSetAttribute $sDocView .= "
".Dict::Format('UI:OpenDocumentInNewWindow_',$oPrevDoc->GetDisplayLink(get_class($this), $this->GetKey(), 'prevdata')).", \n"; $sDocView .= Dict::Format('UI:DownloadDocument_', $oPrevDoc->GetDownloadLink(get_class($this), $this->GetKey(), 'prevdata'))."\n"; //$sDocView = $oPrevDoc->GetDisplayInline(get_class($this), $this->GetKey(), 'prevdata'); - $sResult = "$sAttName changed, previous value: $sDocView"; + $sResult = Dict::Format('Change:AttName_Changed_PreviousValue_OldValue', $sAttName, $sDocView); + } + return $sResult; + } +} +/** + * Safely record the modification of one way encrypted password + */ +class CMDBChangeOpSetAttributeOneWayPassword extends CMDBChangeOpSetAttribute +{ + public static function Init() + { + $aParams = array + ( + "category" => "core/cmdb", + "key_type" => "", + "name_attcode" => "change", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_changeop_setatt_pwd", + "db_key_field" => "id", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeOneWayPassword("prev_pwd", array("sql" => 'data', "default_value" => '', "is_null_allowed"=> true, "allowed_values" => null, "depends_on"=>array()))); + + // Display lists + MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for a list + } + + /** + * Describe (as a text string) the modifications corresponding to this change + */ + public function GetDescription() + { + // Temporary, until we change the options of GetDescription() -needs a more global revision + $bIsHtml = true; + + $sResult = ''; + $oTargetObjectClass = $this->Get('objclass'); + $oTargetObjectKey = $this->Get('objkey'); + $oTargetSearch = new DBObjectSearch($oTargetObjectClass); + $oTargetSearch->AddCondition('id', $oTargetObjectKey, '='); + + $oMonoObjectSet = new DBObjectSet($oTargetSearch); + if (UserRights::IsActionAllowedOnAttribute($this->Get('objclass'), $this->Get('attcode'), UR_ACTION_READ, $oMonoObjectSet) == UR_ALLOWED_YES) + { + $oAttDef = MetaModel::GetAttributeDef($this->Get('objclass'), $this->Get('attcode')); + $sAttName = $oAttDef->GetLabel(); + $sResult = Dict::Format('Change:AttName_Changed', $sAttName); + } + return $sResult; + } +} + +/** + * Safely record the modification of an encrypted field + */ +class CMDBChangeOpSetAttributeEncrypted extends CMDBChangeOpSetAttribute +{ + public static function Init() + { + $aParams = array + ( + "category" => "core/cmdb", + "key_type" => "", + "name_attcode" => "change", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_changeop_setatt_encrypted", + "db_key_field" => "id", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeEncryptedString("prevstring", array("sql" => 'data', "default_value" => '', "is_null_allowed"=> true, "allowed_values" => null, "depends_on"=>array()))); + + // Display lists + MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'attcode')); // Attributes to be displayed for a list + } + + /** + * Describe (as a text string) the modifications corresponding to this change + */ + public function GetDescription() + { + // Temporary, until we change the options of GetDescription() -needs a more global revision + $bIsHtml = true; + + $sResult = ''; + $oTargetObjectClass = $this->Get('objclass'); + $oTargetObjectKey = $this->Get('objkey'); + $oTargetSearch = new DBObjectSearch($oTargetObjectClass); + $oTargetSearch->AddCondition('id', $oTargetObjectKey, '='); + + $oMonoObjectSet = new DBObjectSet($oTargetSearch); + if (UserRights::IsActionAllowedOnAttribute($this->Get('objclass'), $this->Get('attcode'), UR_ACTION_READ, $oMonoObjectSet) == UR_ALLOWED_YES) + { + $oAttDef = MetaModel::GetAttributeDef($this->Get('objclass'), $this->Get('attcode')); + $sAttName = $oAttDef->GetLabel(); + $sPrevString = $this->Get('prevstring'); + $sResult = Dict::Format('Change:AttName_Changed_PreviousValue_OldValue', $sAttName, $sPrevString); } return $sResult; } @@ -370,7 +474,7 @@ class CMDBChangeOpSetAttributeText extends CMDBChangeOpSetAttribute $sTextView = '
'.$this->GetAsHtml('prevdata').'
'; //$sDocView = $oPrevDoc->GetDisplayInline(get_class($this), $this->GetKey(), 'prevdata'); - $sResult = "$sAttName changed, previous value: $sTextView"; + $sResult = Dict::Format('Change:AttName_Changed_PreviousValue_OldValue', $sAttName, $sTextView); } return $sResult; } diff --git a/core/cmdbobject.class.inc.php b/core/cmdbobject.class.inc.php index 3a8de347c..50ba69a6b 100644 --- a/core/cmdbobject.class.inc.php +++ b/core/cmdbobject.class.inc.php @@ -187,7 +187,47 @@ abstract class CMDBObject extends DBObject $oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode); if ($oAttDef->IsLinkSet()) continue; // #@# temporary - if ($oAttDef instanceOf AttributeBlob) + if ($oAttDef instanceOf AttributeOneWayPassword) + { + // One Way encrypted passwords' history is stored -one way- encrypted + $oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeOneWayPassword"); + $oMyChangeOp->Set("change", $oChange->GetKey()); + $oMyChangeOp->Set("objclass", get_class($this)); + $oMyChangeOp->Set("objkey", $this->GetKey()); + $oMyChangeOp->Set("attcode", $sAttCode); + + if (array_key_exists($sAttCode, $aOrigValues)) + { + $original = $aOrigValues[$sAttCode]; + } + else + { + $original = ''; + } + $oMyChangeOp->Set("prev_pwd", $original); + $iId = $oMyChangeOp->DBInsertNoReload(); + } + elseif ($oAttDef instanceOf AttributeEncryptedString) + { + // Encrypted string history is stored encrypted + $oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeEncrypted"); + $oMyChangeOp->Set("change", $oChange->GetKey()); + $oMyChangeOp->Set("objclass", get_class($this)); + $oMyChangeOp->Set("objkey", $this->GetKey()); + $oMyChangeOp->Set("attcode", $sAttCode); + + if (array_key_exists($sAttCode, $aOrigValues)) + { + $original = $aOrigValues[$sAttCode]; + } + else + { + $original = ''; + } + $oMyChangeOp->Set("prevdata", $original); + $iId = $oMyChangeOp->DBInsertNoReload(); + } + elseif ($oAttDef instanceOf AttributeBlob) { // Data blobs $oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeBlob"); diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 8b83ed22b..1afedc700 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -46,6 +46,7 @@ define ('DEFAULT_SECURE_CONNECTION_REQUIRED', false); define ('DEFAULT_HTTPS_HYPERLINKS', false); define ('DEFAULT_ALLOWED_LOGIN_TYPES', 'form|basic|external'); define ('DEFAULT_EXT_AUTH_VARIABLE', '$_SERVER[\'REMOTE_USER\']'); +define ('DEFAULT_ENCRYPTION_KEY', '@iT0pEncr1pti0n!'); // We'll use a random value, later... /** * Config @@ -126,6 +127,13 @@ class Config */ protected $m_sExtAuthVariable; + /** + * @var string Encryption key used for all attributes of type "encrypted string". Can be set to a random value + * unless you want to import a database from another iTop instance, in which case you must use + * the same encryption key in order to properly decode the encrypted fields + */ + protected $m_sEncryptionKey; + public function __construct($sConfigFile, $bLoadConfig = true) { $this->m_sFile = $sConfigFile; @@ -177,6 +185,7 @@ class Config $this->m_sDefaultLanguage = 'EN US'; $this->m_sAllowedLoginTypes = DEFAULT_ALLOWED_LOGIN_TYPES; $this->m_sExtAuthVariable = DEFAULT_EXT_AUTH_VARIABLE; + $this->m_sEncryptionKey = DEFAULT_ENCRYPTION_KEY; $this->m_aModuleSettings = array(); @@ -278,6 +287,7 @@ class Config $this->m_sDefaultLanguage = isset($MySettings['default_language']) ? trim($MySettings['default_language']) : 'EN US'; $this->m_sAllowedLoginTypes = isset($MySettings['allowed_login_types']) ? trim($MySettings['allowed_login_types']) : DEFAULT_ALLOWED_LOGIN_TYPES; $this->m_sExtAuthVariable = isset($MySettings['ext_auth_variable']) ? trim($MySettings['ext_auth_variable']) : DEFAULT_EXT_AUTH_VARIABLE; + $this->m_sEncryptionKey = isset($MySettings['encryption_key']) ? trim($MySettings['encryption_key']) : DEFAULT_ENCRYPTION_KEY; } protected function Verify() @@ -430,6 +440,10 @@ class Config return $this->m_sDefaultLanguage; } + public function GetEncryptionKey() + { + return $this->m_sEncryptionKey; + } public function GetAllowedLoginTypes() { @@ -531,6 +545,11 @@ class Config $this->m_sExtAuthVariable = $sExtAuthVariable; } + public function SetEncryptionKey($sKey) + { + $this->m_sEncryptionKey = $sKey; + } + public function FileIsWritable() { return is_writable($this->m_sFile); @@ -580,6 +599,7 @@ class Config fwrite($hFile, "\t'https_hyperlinks' => ".($this->m_bHttpsHyperlinks ? 'true' : 'false').",\n"); fwrite($hFile, "\t'default_language' => '{$this->m_sDefaultLanguage}',\n"); fwrite($hFile, "\t'allowed_login_types' => '{$this->m_sAllowedLoginTypes}',\n"); + fwrite($hFile, "\t'encryption_key' => '{$this->m_sEncryptionKey}',\n"); fwrite($hFile, ");\n"); fwrite($hFile, "\n"); diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 6b9ab63e6..4c242009d 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -3216,6 +3216,11 @@ abstract class MetaModel return self::$m_oConfig->GetModuleSetting($sModule, $sProperty, $defaultvalue); } + public static function GetConfig() + { + return self::$m_oConfig; + } + protected static $m_aPlugins = array(); public static function RegisterPlugin($sType, $sName, $aInitCallSpec = array()) { diff --git a/core/ormpassword.class.inc.php b/core/ormpassword.class.inc.php new file mode 100644 index 000000000..217076480 --- /dev/null +++ b/core/ormpassword.class.inc.php @@ -0,0 +1,119 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + * @package itopORM + */ + +class ormPassword +{ + protected $m_sHashed; + protected $m_sSalt; + + /** + * Constructor, initializes the password from the encrypted values + */ + public function __construct($sHash = '', $sSalt = '') + { + $this->m_sHashed = $sHash; + $this->m_sSalt = $sSalt; + } + + /** + * Encrypts the clear text password, with a unique salt + */ + public function SetPassword($sClearTextPassword) + { + $this->m_sSalt = SimpleCrypt::GetNewSalt(); + $this->m_sHashed = $this->ComputeHash($sClearTextPassword); + } + + /** + * Print the password: displays some stars + * @return string + */ + public function __toString() + { + return '*****'; // Password can not be read + } + + public function IsEmpty() + { + return ($this->m_hashed == null); + } + + public function GetHash() + { + return $this->m_sHashed; + } + + public function GetSalt() + { + return $this->m_sSalt; + } + + /** + * Displays the password: displays some stars + * @return string + */ + public function GetAsHTML() + { + return '*****'; // Password can not be read + } + + /** + * Check if the supplied clear text password matches the encrypted one + * @param string $sClearTextPassword + * @return boolean True if it matches, false otherwise + */ + public function CheckPassword($sClearTextPassword) + { + $bResult = false; + $sHashedPwd = $this->ComputeHash($sClearTextPassword); + if ($this->m_sHashed == $sHashedPwd) + { + $bResult = true; + } + return $bResult; + } + + /** + * Computes the hashed version of a password using a unique salt + * for this password. A unique salt is generated if needed + * @return string + */ + protected function ComputeHash($sClearTextPwd) + { + if ($this->m_sSalt == null) + { + $this->m_sSalt = SimpleCrypt::GetNewSalt(); + } + return hash('sha256', $this->m_sSalt.$sClearTextPwd); + } +} +?> \ No newline at end of file diff --git a/core/simplecrypt.class.inc.php b/core/simplecrypt.class.inc.php new file mode 100644 index 000000000..f2bf00a8d --- /dev/null +++ b/core/simplecrypt.class.inc.php @@ -0,0 +1,235 @@ +encrypt('a_key','the_text'); + * $sClearText = $oSimpleCrypt->decrypt('a_key',$encrypted); + * + * The result is $plain equals to 'the_text' + * + * You can use a different engine if you don't have Mcrypt: + * $oSimpleCrypt = new SimpleCrypt('Simple'); + * + * A string encrypted with one engine can't be decrypted with + * a different one even if the key is the same. + * + * @author Miguel Ros + * @author Erwan Taloc + * @author Romain Quetiez + * @author Denis Flaven + * @version 0.3 + * @license GPL + */ + +class SimpleCrypt +{ + /** + * Constructor + * @param string $sEngineName Engine for encryption. Values: Simple, Mcrypt + */ + function __construct($sEngineName = 'Mcrypt') + { + if (($sEngineName == 'Mcrypt') && (!function_exists('mcrypt_module_open'))) + { + // Defaults to Simple encryption if the mcrypt module is not present + $sEngineName = 'Simple'; + } + $sEngineName = 'SimpleCrypt' . $sEngineName . 'Engine'; + $this->oEngine = new $sEngineName; + } + + /** + * Encrypts the string with the given key + * @param string $key + * @param string $sString Plaintext string + * @return string Ciphered string + */ + function Encrypt($key, $sString) + { + return $this->oEngine->Encrypt($key,$sString); + } + + + /** + * Decrypts the string by the given key + * @param string $key + * @param string $string Ciphered string + * @return string Plaintext string + */ + function Decrypt($key, $string) + { + return $this->oEngine->Decrypt($key,$string); + } + + /** + * Returns a random "salt" value, to be used when "hashing" a password + * using a one-way encryption algorithm, to prevent an attack using a "rainbow table" + * Tryes to use the best available random number generator + * @return string The generated random "salt" + */ + static function GetNewSalt() + { + // Copied from http://www.php.net/manual/en/function.mt-rand.php#83655 + // get 128 pseudorandom bits in a string of 16 bytes + + $sRandomBits = null; + + // Unix/Linux platform? + $fp = @fopen('/dev/urandom','rb'); + if ($fp !== FALSE) + { + //echo "Random bits pulled from /dev/urandom
\n"; + $sRandomBits .= @fread($fp,16); + @fclose($fp); + } + else + { + // MS-Windows platform? + if (@class_exists('COM')) + { + // http://msdn.microsoft.com/en-us/library/aa388176(VS.85).aspx + try + { + $CAPI_Util = new COM('CAPICOM.Utilities.1'); + $sBase64RandomBits = ''.$CAPI_Util->GetRandom(16,0); + + // if we ask for binary data PHP munges it, so we + // request base64 return value. We squeeze out the + // redundancy and useless ==CRLF by hashing... + if ($sBase64RandomBits) + { + //echo "Random bits got from CAPICOM.Utilities.1
\n"; + $sRandomBits = md5($sBase64RandomBits, TRUE); + } + } + catch (Exception $ex) + { + // echo 'Exception: ' . $ex->getMessage(); + } + } + } + if ($sRandomBits == null) + { + // No "strong" random generator available, use PHP's built-in mechanism + //echo "Random bits generated from mt_rand
\n"; + mt_srand(crc32(microtime())); + $sRandomBits = ''; + for($i = 0; $i < 4; $i++) + { + $sRandomBits .= sprintf('%04x', mt_rand(0, 65535)); + } + + + } + return $sRandomBits; + } +} + +/** + * Interface for encryption engines + */ +interface CryptEngine +{ + function Encrypt($key, $sString); + function Decrypt($key, $encrypted_data); +} + +/** + * Simple Engine doesn't need any PHP extension. + * Every encryption of the same string with the same key + * will return the same encrypted string + */ +class SimpleCryptSimpleEngine implements CryptEngine +{ + public function Encrypt($key, $sString) + { + $result = ''; + for($i=1; $i<=strlen($sString); $i++) + { + $char = substr($sString, $i-1, 1); + $keychar = substr($key, ($i % strlen($key))-1, 1); + $char = chr(ord($char)+ord($keychar)); + $result.=$char; + } + return $result; + } + + public function Decrypt($key, $encrypted_data) + { + $result = ''; + for($i=1; $i<=strlen($encrypted_data); $i++) + { + $char = substr($encrypted_data, $i-1, 1); + $keychar = substr($key, ($i % strlen($key))-1, 1); + $char = chr(ord($char)-ord($keychar)); + $result.=$char; + } + return $result; + } +} + +/** + * McryptEngine requires Mcrypt extension + * Every encryption of the same string with the same key + * will return a different encrypted string. + */ +class SimpleCryptMcryptEngine implements CryptEngine +{ + var $alg = MCRYPT_BLOWFISH; + var $td = null; + + public function __construct() + { + $this->td = mcrypt_module_open($this->alg,'','cbc',''); + } + + public function Encrypt($key, $sString) + { + $iv = mcrypt_create_iv (mcrypt_enc_get_iv_size($this->td), MCRYPT_RAND); // MCRYPT_RAND is the only choice on Windows prior to PHP 5.3 + mcrypt_generic_init($this->td, $key, $iv); + if (empty($sString)) + { + $sString = str_repeat("\0", 8); + } + $encrypted_data = mcrypt_generic($this->td, $sString); + mcrypt_generic_deinit($this->td); + return $iv.$encrypted_data; + } + + public function Decrypt($key, $encrypted_data) + { + $iv = substr($encrypted_data, 0, mcrypt_enc_get_iv_size($this->td)); + $string = substr($encrypted_data, mcrypt_enc_get_iv_size($this->td)); + mcrypt_generic_init($this->td, $key, $iv); + $decrypted_data = rtrim(mdecrypt_generic($this->td, $string), "\0"); + mcrypt_generic_deinit($this->td); + return $decrypted_data; + } + + public function __destruct() + { + mcrypt_module_close($this->td); + } +} +?> \ No newline at end of file diff --git a/dictionaries/dictionary.itop.core.php b/dictionaries/dictionary.itop.core.php index e72642336..5a7afaa8a 100644 --- a/dictionaries/dictionary.itop.core.php +++ b/dictionaries/dictionary.itop.core.php @@ -104,6 +104,15 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:CMDBChangeOpSetAttributeScalar/Attribute:newvalue' => 'New value', 'Class:CMDBChangeOpSetAttributeScalar/Attribute:newvalue+' => 'new value of the attribute', )); +// Used by CMDBChangeOp... & derived classes +Dict::Add('EN US', 'English', 'English', array( + 'Change:ObjectCreated' => 'Object created', + 'Change:ObjectDeleted' => 'Object deleted', + 'Change:AttName_SetTo_NewValue_PreviousValue_OldValue' => '%1$s set to %2$s (previous value: %3$s)', + 'Change:Text_AppendedTo_AttName' => '%1$s appended to %2$s', + 'Change:AttName_Changed_PreviousValue_OldValue' => '%1$s modified, previous value: %2$s', + 'Change:AttName_Changed' => '%1$s modified', +)); // // Class: CMDBChangeOpSetAttributeBlob diff --git a/dictionaries/dictionary.itop.ui.php b/dictionaries/dictionary.itop.ui.php index eddc4b8d4..ac399abf0 100644 --- a/dictionaries/dictionary.itop.ui.php +++ b/dictionaries/dictionary.itop.ui.php @@ -315,7 +315,7 @@ Dict::Add('EN US', 'English', 'English', array(

', - 'UI:WelcomeMenu:MyCalls' => 'User Requests assigned to me', + 'UI:WelcomeMenu:MyCalls' => 'My requests', 'UI:WelcomeMenu:MyIncidents' => 'Incidents assigned to me', 'UI:AllOrganizations' => ' All Organizations ', 'UI:YourSearch' => 'Your Search', @@ -345,9 +345,11 @@ Dict::Add('EN US', 'English', 'English', array( 'UI:Button:Create' => ' Create ', 'UI:Button:Delete' => ' Delete ! ', 'UI:Button:ChangePassword' => ' Change Password ', - + 'UI:Button:ResetPassword' => ' Reset Password ', + 'UI:SearchToggle' => 'Search', 'UI:ClickToCreateNew' => 'Click here to create a new %1$s', + 'UI:SearchFor_Class' => 'Search for %1$s objects', 'UI:NoObjectToDisplay' => 'No object to display.', 'UI:Error:MandatoryTemplateParameter_object_id' => 'Parameter object_id is mandatory when link_attr is specified. Check the definition of the display template.', 'UI:Error:MandatoryTemplateParameter_target_attr' => 'Parameter target_attr is mandatory when link_attr is specified. Check the definition of the display template.', @@ -383,6 +385,7 @@ Dict::Add('EN US', 'English', 'English', array( 'UI:GroupBy:Count' => 'Count', 'UI:GroupBy:Count+' => 'Number of elements', 'UI:CountOfObjects' => '%1$d objects matching the criteria.', + 'UI_CountOfObjectsShort' => '%1$d objects.', 'UI:NoObject_Class_ToDisplay' => 'No %1$s to display', 'UI:History:LastModified_On_By' => 'Last modified on %1$s by %2$s.', 'UI:HistoryTab' => 'History', @@ -818,6 +821,7 @@ When associated with a trigger, each action is given an "order" number, specifyi 'UI:Deadline_Hours_Minutes' => '%1$dh %2$dmin', 'UI:Deadline_Days_Hours_Minutes' => '%1$dd %2$dh %3$dmin', 'UI:Help' => 'Help', + 'UI:PasswordConfirm' => '(Confirm)', )); diff --git a/dictionaries/es_cr.dictionary.itop.core.php b/dictionaries/es_cr.dictionary.itop.core.php index e56d1c89a..e6abb9f8a 100644 --- a/dictionaries/es_cr.dictionary.itop.core.php +++ b/dictionaries/es_cr.dictionary.itop.core.php @@ -104,6 +104,13 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( 'Class:CMDBChangeOpSetAttributeScalar/Attribute:newvalue' => 'Nuevo valor', 'Class:CMDBChangeOpSetAttributeScalar/Attribute:newvalue+' => 'nuevo valor del atributo', )); +// Used by CMDBChangeOp... & derived classes +Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( + 'Change:AttName_SetTo_NewValue_PreviousValue_OldValue' => '%1$s modificado en %2$s (valor anterior: %3$s)', + 'Change:Text_AppendedTo_AttName' => '%1$s añadido a %2$s', + 'Change:AttName_Changed_PreviousValue_OldValue' => '%1$s modificado, valor anterior: %2$s', + 'Change:AttName_Changed' => '%1$s modificado', +)); // // Class: CMDBChangeOpSetAttributeBlob diff --git a/dictionaries/es_cr.dictionary.itop.ui.php b/dictionaries/es_cr.dictionary.itop.ui.php index 433ade8d2..a6fa853ba 100644 --- a/dictionaries/es_cr.dictionary.itop.ui.php +++ b/dictionaries/es_cr.dictionary.itop.ui.php @@ -331,7 +331,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(

', - 'UI:WelcomeMenu:MyCalls' => 'User Requests assigned to me', + 'UI:WelcomeMenu:MyCalls' => 'My requests', 'UI:WelcomeMenu:MyIncidents' => 'Incidents assigned to me', 'UI:AllOrganizations' => ' All Organizations ', 'UI:YourSearch' => 'Your Search', @@ -361,6 +361,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( 'UI:Button:Create' => ' Create ', 'UI:Button:Delete' => ' Delete ! ', 'UI:Button:ChangePassword' => ' Change Password ', + 'UI:Button:ResetPassword' => ' Reset Password ', 'UI:SearchToggle' => 'Search', 'UI:ClickToCreateNew' => 'Click here to create a new %1$s', @@ -399,6 +400,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( 'UI:GroupBy:Count' => 'Count', 'UI:GroupBy:Count+' => 'Number of elements', 'UI:CountOfObjects' => '%1$d objects matching the criteria.', + 'UI_CountOfObjectsShort' => '%1$d objects.', 'UI:NoObject_Class_ToDisplay' => 'No %1$s to display', 'UI:History:LastModified_On_By' => 'Last modified on %1$s by %2$s.', 'UI:HistoryTab' => 'History', @@ -829,6 +831,7 @@ When associated with a trigger, each action is given an "order" number, specifyi 'UI:Deadline_Hours_Minutes' => '%1$dh %2$dmin', 'UI:Deadline_Days_Hours_Minutes' => '%1$dd %2$dh %3$dmin', 'UI:Help' => 'Ayuda', + 'UI:PasswordConfirm' => '(Confirm)', )); ?> diff --git a/dictionaries/fr.dictionary.itop.core.php b/dictionaries/fr.dictionary.itop.core.php index 742e75ec8..468f307c0 100644 --- a/dictionaries/fr.dictionary.itop.core.php +++ b/dictionaries/fr.dictionary.itop.core.php @@ -33,7 +33,7 @@ // Class: CMDBChange // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:CMDBChange' => 'change', 'Class:CMDBChange+' => 'Changes tracking', 'Class:CMDBChange/Attribute:date' => 'date', @@ -46,7 +46,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: CMDBChangeOp // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:CMDBChangeOp' => 'change operation', 'Class:CMDBChangeOp+' => 'Change operations tracking', 'Class:CMDBChangeOp/Attribute:change' => 'change', @@ -67,7 +67,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: CMDBChangeOpCreate // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:CMDBChangeOpCreate' => 'object creation', 'Class:CMDBChangeOpCreate+' => 'Object creation tracking', )); @@ -76,7 +76,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: CMDBChangeOpDelete // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:CMDBChangeOpDelete' => 'object deletion', 'Class:CMDBChangeOpDelete+' => 'Object deletion tracking', )); @@ -85,7 +85,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: CMDBChangeOpSetAttribute // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:CMDBChangeOpSetAttribute' => 'object change', 'Class:CMDBChangeOpSetAttribute+' => 'Object properties change tracking', 'Class:CMDBChangeOpSetAttribute/Attribute:attcode' => 'Attribute', @@ -96,7 +96,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: CMDBChangeOpSetAttributeScalar // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:CMDBChangeOpSetAttributeScalar' => 'property change', 'Class:CMDBChangeOpSetAttributeScalar+' => 'Object scalar properties change tracking', 'Class:CMDBChangeOpSetAttributeScalar/Attribute:oldvalue' => 'Previous value', @@ -104,12 +104,21 @@ Dict::Add('EN US', 'French', 'Français', array( 'Class:CMDBChangeOpSetAttributeScalar/Attribute:newvalue' => 'New value', 'Class:CMDBChangeOpSetAttributeScalar/Attribute:newvalue+' => 'new value of the attribute', )); +// Used by CMDBChangeOp... & derived classes +Dict::Add('FR FR', 'French', 'Français', array( + 'Change:ObjectCreated' => 'Elément créé', + 'Change:ObjectDeleted' => 'Elément effacé', + 'Change:AttName_SetTo_NewValue_PreviousValue_OldValue' => '%1$s modifié en %2$s (ancienne valeur: %3$s)', + 'Change:Text_AppendedTo_AttName' => '%1$s ajouté à %2$s', + 'Change:AttName_Changed_PreviousValue_OldValue' => '%1$s modifié, ancienne valeur: %2$s', + 'Change:AttName_Changed' => '%1$s modifié', +)); // // Class: CMDBChangeOpSetAttributeBlob // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:CMDBChangeOpSetAttributeBlob' => 'data change', 'Class:CMDBChangeOpSetAttributeBlob+' => 'data change tracking', 'Class:CMDBChangeOpSetAttributeBlob/Attribute:prevdata' => 'Previous data', @@ -120,7 +129,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: CMDBChangeOpSetAttributeText // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:CMDBChangeOpSetAttributeText' => 'text change', 'Class:CMDBChangeOpSetAttributeText+' => 'text change tracking', 'Class:CMDBChangeOpSetAttributeText/Attribute:prevdata' => 'Previous data', @@ -131,7 +140,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: Event // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:Event' => 'Log Event', 'Class:Event+' => 'An application internal event', 'Class:Event/Attribute:message' => 'message', @@ -148,7 +157,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: EventNotification // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:EventNotification' => 'Notification event', 'Class:EventNotification+' => 'Trace of a notification that has been sent', 'Class:EventNotification/Attribute:trigger_id' => 'Trigger', @@ -163,7 +172,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: EventNotificationEmail // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:EventNotificationEmail' => 'Email emission event', 'Class:EventNotificationEmail+' => 'Trace of an email that has been sent', 'Class:EventNotificationEmail/Attribute:to' => 'TO', @@ -184,7 +193,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: EventIssue // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:EventIssue' => 'Issue event', 'Class:EventIssue+' => 'Trace of an issue (warning, error, etc.)', 'Class:EventIssue/Attribute:issue' => 'Issue', @@ -207,7 +216,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: EventWebService // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:EventWebService' => 'Web service event', 'Class:EventWebService+' => 'Trace of an web service call', 'Class:EventWebService/Attribute:verb' => 'Verb', @@ -228,7 +237,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: Action // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:Action' => 'action', 'Class:Action+' => 'Custom action', 'Class:Action/Attribute:name' => 'Name', @@ -253,7 +262,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: ActionNotification // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:ActionNotification' => 'notification', 'Class:ActionNotification+' => 'Notification (abstract)', )); @@ -262,7 +271,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: ActionEmail // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:ActionEmail' => 'email notification', 'Class:ActionEmail+' => 'Action: Email notification', 'Class:ActionEmail/Attribute:test_recipient' => 'Test recipient', @@ -295,7 +304,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: Trigger // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:Trigger' => 'trigger', 'Class:Trigger+' => 'Custom event handler', 'Class:Trigger/Attribute:description' => 'Description', @@ -310,7 +319,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: TriggerOnObject // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:TriggerOnObject' => 'Trigger on a class of objects', 'Class:TriggerOnObject+' => 'Trigger on a given class of objects', 'Class:TriggerOnObject/Attribute:target_class' => 'Target class', @@ -321,7 +330,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: TriggerOnStateChange // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:TriggerOnStateChange' => 'Trigger on object state change', 'Class:TriggerOnStateChange+' => 'Trigger on object state change', 'Class:TriggerOnStateChange/Attribute:state' => 'State', @@ -332,7 +341,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: TriggerOnStateEnter // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:TriggerOnStateEnter' => 'Trigger on object entering a state', 'Class:TriggerOnStateEnter+' => 'Trigger on object state change - entering', )); @@ -341,7 +350,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: TriggerOnStateLeave // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:TriggerOnStateLeave' => 'Trigger on object leaving a state', 'Class:TriggerOnStateLeave+' => 'Trigger on object state change - leaving', )); @@ -350,7 +359,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: TriggerOnObjectCreate // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:TriggerOnObjectCreate' => 'Trigger on object creation', 'Class:TriggerOnObjectCreate+' => 'Trigger on object creation of [a child class of] the given class', )); @@ -359,7 +368,7 @@ Dict::Add('EN US', 'French', 'Français', array( // Class: lnkTriggerAction // -Dict::Add('EN US', 'French', 'Français', array( +Dict::Add('FR FR', 'French', 'Français', array( 'Class:lnkTriggerAction' => 'Actions-Trigger', 'Class:lnkTriggerAction+' => 'Link between a trigger and an action', 'Class:lnkTriggerAction/Attribute:action_id' => 'Action', diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index 7693f56b5..cbf576f00 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -346,6 +346,7 @@ Dict::Add('FR FR', 'French', 'Français', array( 'UI:Button:Create' => ' Créer ', 'UI:Button:Delete' => ' Supprimer ! ', 'UI:Button:ChangePassword' => ' Changer ! ', + 'UI:Button:ResetPassword' => ' Ràz du mot de passe ', 'UI:SearchToggle' => 'Recherche', @@ -384,6 +385,7 @@ Dict::Add('FR FR', 'French', 'Français', array( 'UI:GroupBy:Count' => 'Nombre', 'UI:GroupBy:Count+' => 'Nombre d\'éléments', 'UI:CountOfObjects' => '%1$d objets correspondants aux critères.', + 'UI_CountOfObjectsShort' => '%1$d objets.', 'UI:NoObject_Class_ToDisplay' => 'Aucun objet %1$s à afficher', 'UI:History:LastModified_On_By' => 'Dernière modification par %2$s le %1$s.', 'UI:HistoryTab' => 'Historique', @@ -829,6 +831,7 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé 'UI:Deadline_Hours_Minutes' => '%1$dh %2$dmin', 'UI:Deadline_Days_Hours_Minutes' => '%1$dj %2$dh %3$dmin', 'UI:Help' => 'Aide', + 'UI:PasswordConfirm' => '(Confirmer)', )); ?> diff --git a/js/forms-json-utils.js b/js/forms-json-utils.js index 2588c61d6..f0f059768 100644 --- a/js/forms-json-utils.js +++ b/js/forms-json-utils.js @@ -207,3 +207,45 @@ function UpdateDependentFields(aFieldNames) } oWizardHelper.AjaxQueryServer(); } + +function ResetPwd(id) +{ + // Reset the values of the password fields + $('#'+id).val('*****'); + $('#'+id+'_confirm').val('*****'); + // And reset the flag, to tell it that the password remains unchanged + $('#'+id+'_changed').val(0); + // Visual feedback, None when it's Ok + $('#v_'+id).html(''); +} + +// Called whenever the content of a one way encrypted password changes +function PasswordFieldChanged(id) +{ + // Set the flag, to tell that the password changed + console.log('Password changed'); + $('#'+id+'_changed').val(1); +} + +// Special validation function for one way encrypted password fields +function ValidatePasswordField(id, sFormId) +{ + var bChanged = $('#'+id+'_changed').val(); + if (bChanged) + { + if ($('#'+id).val() != $('#'+id+'_confirm').val()) + { + oFormErrors['err_'+sFormId]++; + if (oFormErrors['input_'+sFormId] == null) + { + // Let's remember the first input with an error, so that we can put back the focus on it later + oFormErrors['input_'+sFormId] = id; + } + // Visual feedback + $('#v_'+id).html(''); + return false; + } + } + $('#v_'+id).html(''); //'); + return true; +} \ No newline at end of file diff --git a/modules/authent-local/model.authent-local.php b/modules/authent-local/model.authent-local.php index 1ac7f6371..3da9c9ca1 100644 --- a/modules/authent-local/model.authent-local.php +++ b/modules/authent-local/model.authent-local.php @@ -44,8 +44,7 @@ class UserLocal extends UserInternal MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributePassword("password", array("allowed_values"=>null, "sql"=>"pwd", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeEncryptedString("encrypted_password", array("allowed_values"=>null, "sql"=>"encrypted_pwd", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeOneWayPassword("password", array("allowed_values"=>null, "sql"=>"pwd", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); // Display lists MetaModel::Init_SetZListItems('details', array('contactid', 'first_name', 'email', 'login', 'password', 'language', 'profile_list', 'allowed_org_list')); // Attributes to be displayed for the complete details @@ -57,7 +56,14 @@ class UserLocal extends UserInternal public function CheckCredentials($sPassword) { - if ($this->Get('password') == $sPassword) +// if ($this->Get('password') == $sPassword) +// { +// return true; +// } + $oPassword = $this->Get('password'); // ormPassword object + // Cannot compare directly the values since they are hashed, so + // Let's ask the password to compare the hashed values + if ($oPassword->CheckPassword($sPassword)) { return true; } diff --git a/modules/itop-change-mgmt-1.0.0/en.dict.itop-change-mgmt.php b/modules/itop-change-mgmt-1.0.0/en.dict.itop-change-mgmt.php index 6e983436f..9e79444b0 100644 --- a/modules/itop-change-mgmt-1.0.0/en.dict.itop-change-mgmt.php +++ b/modules/itop-change-mgmt-1.0.0/en.dict.itop-change-mgmt.php @@ -27,6 +27,12 @@ Dict::Add('EN US', 'English', 'English', array( 'Menu:ChangeManagement' => 'Change management', 'Menu:Change:Overview' => 'Overview', 'Menu:Change:Overview+' => '', + 'Menu:NewChange' => 'New Change', + 'Menu:NewChange+' => 'Create a new Change ticket', + 'Menu:SearchChanges' => 'Search for Changes', + 'Menu:SearchChanges+' => 'Search for Change tickets', + 'Menu:Change:Shortcuts' => 'Shortcuts', + 'Menu:Change:Shortcuts+' => '', 'Menu:WaitingAcceptance' => 'Changes awaiting acceptance', 'Menu:WaitingAcceptance+' => '', 'Menu:WaitingApproval' => 'Changes awaiting approval', diff --git a/modules/itop-change-mgmt-1.0.0/es_cr.dict.itop-change-mgmt.php b/modules/itop-change-mgmt-1.0.0/es_cr.dict.itop-change-mgmt.php index 68a72e3e3..526d30237 100644 --- a/modules/itop-change-mgmt-1.0.0/es_cr.dict.itop-change-mgmt.php +++ b/modules/itop-change-mgmt-1.0.0/es_cr.dict.itop-change-mgmt.php @@ -27,6 +27,12 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( 'Menu:ChangeManagement' => 'Gestión del cambio', 'Menu:Change:Overview' => 'Visión General', 'Menu:Change:Overview+' => '', + 'Menu:NewChange' => 'New Change', + 'Menu:NewChange+' => 'Create a new Change ticket', + 'Menu:SearchChanges' => 'Search for Changes', + 'Menu:SearchChanges+' => 'Search for Change tickets', + 'Menu:Change:Shortcuts' => 'Shortcuts', + 'Menu:Change:Shortcuts+' => '', 'Menu:WaitingAcceptance' => 'Cambios esperando ser aceptados', 'Menu:WaitingAcceptance+' => '', 'Menu:WaitingApproval' => 'Cambios esperando ser aprovados', diff --git a/modules/itop-change-mgmt-1.0.0/fr.dict.itop-change-mgmt.php b/modules/itop-change-mgmt-1.0.0/fr.dict.itop-change-mgmt.php index 3ead4d25a..8d5fdb271 100644 --- a/modules/itop-change-mgmt-1.0.0/fr.dict.itop-change-mgmt.php +++ b/modules/itop-change-mgmt-1.0.0/fr.dict.itop-change-mgmt.php @@ -27,6 +27,12 @@ Dict::Add('FR FR', 'French', 'Français', array( 'Menu:ChangeManagement' => 'Gestion des changements', 'Menu:Change:Overview' => 'Vue d\'ensemble', 'Menu:Change:Overview+' => '', + 'Menu:NewChange' => 'Nouveau changement', + 'Menu:NewChange+' => 'Créer un nouveau ticket de changement', + 'Menu:SearchChanges' => 'Rechercher des changements', + 'Menu:SearchChanges+' => 'Rechercher parmi les tickets de changement', + 'Menu:Change:Shortcuts' => 'Raccourcis', + 'Menu:Change:Shortcuts+' => '', 'Menu:WaitingAcceptance' => 'Tickets en attente d\'acceptance', 'Menu:WaitingAcceptance+' => '', 'Menu:WaitingApproval' => 'Tickets en attente d\'approbation', diff --git a/modules/itop-change-mgmt-1.0.0/model.itop-change-mgmt.php b/modules/itop-change-mgmt-1.0.0/model.itop-change-mgmt.php index c7cb5e766..3d7c8cda0 100644 --- a/modules/itop-change-mgmt-1.0.0/model.itop-change-mgmt.php +++ b/modules/itop-change-mgmt-1.0.0/model.itop-change-mgmt.php @@ -483,9 +483,12 @@ class EmergencyChange extends ApprovedChange $oMyMenuGroup = new MenuGroup('ChangeManagement', 50 /* fRank */); new TemplateMenuNode('Change:Overview', '../modules/itop-change-mgmt-1.0.0/overview.html', $oMyMenuGroup->GetIndex() /* oParent */, 0 /* fRank */); -new OQLMenuNode('MyChanges', 'SELECT Change WHERE agent_id = :current_contact_id', $oMyMenuGroup->GetIndex(), 1 /* fRank */); -new OQLMenuNode('Changes', 'SELECT Change WHERE status != "closed"', $oMyMenuGroup->GetIndex(), 2 /* fRank */); -new OQLMenuNode('WaitingApproval', 'SELECT ApprovedChange WHERE status IN ("plannedscheduled")', $oMyMenuGroup->GetIndex(), 3 /* fRank */); -new OQLMenuNode('WaitingAcceptance', 'SELECT NormalChange WHERE status IN ("new")', $oMyMenuGroup->GetIndex(), 4 /* fRank */); +new NewObjectMenuNode('NewChange', 'Change', $oMyMenuGroup->GetIndex(), 1 /* fRank */); +new SearchMenuNode('SearchChanges', 'Change', $oMyMenuGroup->GetIndex(), 2 /* fRank */); +$oShortcutNode = new TemplateMenuNode('Change:Shortcuts', '', $oMyMenuGroup->GetIndex(), 3 /* fRank */); +new OQLMenuNode('MyChanges', 'SELECT Change WHERE agent_id = :current_contact_id', $oShortcutNode->GetIndex(), 1 /* fRank */); +new OQLMenuNode('Changes', 'SELECT Change WHERE status != "closed"', $oShortcutNode->GetIndex(), 2 /* fRank */); +new OQLMenuNode('WaitingApproval', 'SELECT ApprovedChange WHERE status IN ("plannedscheduled")', $oShortcutNode->GetIndex(), 3 /* fRank */); +new OQLMenuNode('WaitingAcceptance', 'SELECT NormalChange WHERE status IN ("new")', $oShortcutNode->GetIndex(), 4 /* fRank */); ?> diff --git a/modules/itop-config-mgmt-1.0.0/fr.dict.itop-config-mgmt.php b/modules/itop-config-mgmt-1.0.0/fr.dict.itop-config-mgmt.php index 30ec9f9ed..bc041280a 100644 --- a/modules/itop-config-mgmt-1.0.0/fr.dict.itop-config-mgmt.php +++ b/modules/itop-config-mgmt-1.0.0/fr.dict.itop-config-mgmt.php @@ -31,7 +31,10 @@ Dict::Add('FR FR', 'French', 'Français', array( 'Relation:impacts/Description' => 'Eléments impactés par', 'Relation:impacts/VerbUp' => 'Impacte...', - 'Relation:impacts/VerbDown' => 'Eléments impactés par...', + 'Relation:impacts/VerbDown' => 'Dépend de...', + 'Relation:depends on/Description' => 'Eléments dont dépend cet élément', + 'Relation:depends on/VerbUp' => 'Dépend de...', + 'Relation:depends on/VerbDown' => 'Impacte...', )); diff --git a/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php b/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php index 8cf7e05c8..dc984320a 100644 --- a/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php +++ b/modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php @@ -1474,7 +1474,7 @@ new WebPageMenuNode('Audit', '../pages/audit.php', $iAdminGroup, 33 /* fRank */) $oTypologyNode = new TemplateMenuNode('Catalogs', '', $iAdminGroup, 50 /* fRank */); $iTopology = $oTypologyNode->GetIndex(); -new OQLMenuNode('Organization', 'SELECT Organization', $iTopology, 10 /* fRank */); +new OQLMenuNode('Organization', 'SELECT Organization', $iTopology, 10 /* fRank */, true /* bSearch */); new OQLMenuNode('Application', 'SELECT Application', $iTopology, 20 /* fRank */); new OQLMenuNode('DBServer', 'SELECT DBServer', $iTopology, 40 /* fRank */); @@ -1486,24 +1486,28 @@ new TemplateMenuNode('ConfigManagementOverview', '../modules/itop-config-mgmt-1. $oContactNode = new TemplateMenuNode('Contact', '../modules/itop-config-mgmt-1.0.0/contacts_menu.html', $oConfigManagementGroup->GetIndex(), 1 /* fRank */); -new OQLMenuNode('Person', 'SELECT Person', $oContactNode->GetIndex(), 1 /* fRank */); -new OQLMenuNode('Team', 'SELECT Team', $oContactNode->GetIndex(), 2 /* fRank */); +new NewObjectMenuNode('NewContact', 'Contact', $oContactNode->GetIndex(), 1 /* fRank */); +new SearchMenuNode('SearchContacts', 'Contact', $oContactNode->GetIndex(), 2 /* fRank */); +new OQLMenuNode('Person', 'SELECT Person', $oContactNode->GetIndex(), 3 /* fRank */); +new OQLMenuNode('Team', 'SELECT Team', $oContactNode->GetIndex(), 4 /* fRank */); -new OQLMenuNode('Document', 'SELECT Document', $oConfigManagementGroup->GetIndex(), 2 /* fRank */); -new OQLMenuNode('Location', 'SELECT Location', $oConfigManagementGroup->GetIndex(), 3 /* fRank */); +new OQLMenuNode('Document', 'SELECT Document', $oConfigManagementGroup->GetIndex(), 2 /* fRank */, true /* bSearch */); +new OQLMenuNode('Location', 'SELECT Location', $oConfigManagementGroup->GetIndex(), 3 /* fRank */, true /* bSearch */); $oCINode = new TemplateMenuNode('ConfigManagementCI', '../modules/itop-config-mgmt-1.0.0/cis_menu.html', $oConfigManagementGroup->GetIndex(), 4 /* fRank */); +new NewObjectMenuNode('NewCI', 'FunctionalCI', $oCINode->GetIndex(), 0 /* fRank */); +new SearchMenuNode('SearchCIs', 'FunctionalCI', $oCINode->GetIndex(), 1 /* fRank */); -new OQLMenuNode('BusinessProcess', 'SELECT BusinessProcess', $oCINode->GetIndex(), 0 /* fRank */); -new OQLMenuNode('ApplicationSolution', 'SELECT ApplicationSolution', $oCINode->GetIndex(), 1 /* fRank */); +new OQLMenuNode('BusinessProcess', 'SELECT BusinessProcess', $oCINode->GetIndex(), 2 /* fRank */); +new OQLMenuNode('ApplicationSolution', 'SELECT ApplicationSolution', $oCINode->GetIndex(), 3 /* fRank */); -$oSWNode = new TemplateMenuNode('ConfigManagementSoftware', '', $oCINode->GetIndex(), 2 /* fRank */); +$oSWNode = new TemplateMenuNode('ConfigManagementSoftware', '', $oCINode->GetIndex(), 4 /* fRank */); new OQLMenuNode('Licence', 'SELECT Licence', $oSWNode->GetIndex(), 0 /* fRank */); new OQLMenuNode('Patch', 'SELECT Patch', $oSWNode->GetIndex(), 1 /* fRank */); new OQLMenuNode('ApplicationInstance', 'SELECT SoftwareInstance', $oSWNode->GetIndex(), 2 /* fRank */); -$oHWNode = new TemplateMenuNode('ConfigManagementHardware', '', $oCINode->GetIndex(), 3 /* fRank */); +$oHWNode = new TemplateMenuNode('ConfigManagementHardware', '', $oCINode->GetIndex(), 5 /* fRank */); new OQLMenuNode('Subnet', 'SELECT Subnet', $oHWNode->GetIndex(), 0 /* fRank */); new OQLMenuNode('NetworkDevice', 'SELECT NetworkDevice', $oHWNode->GetIndex(), 1 /* fRank */); new OQLMenuNode('Server', 'SELECT Server', $oHWNode->GetIndex(), 2 /* fRank */); diff --git a/modules/itop-incident-mgmt-1.0.0/en.dict.itop-incident-mgmt.php b/modules/itop-incident-mgmt-1.0.0/en.dict.itop-incident-mgmt.php index 7352570ae..837fa7267 100644 --- a/modules/itop-incident-mgmt-1.0.0/en.dict.itop-incident-mgmt.php +++ b/modules/itop-incident-mgmt-1.0.0/en.dict.itop-incident-mgmt.php @@ -28,6 +28,12 @@ Dict::Add('EN US', 'English', 'English', array( 'Menu:IncidentManagement+' => 'Incident Management', 'Menu:Incident:Overview' => 'Overview', 'Menu:Incident:Overview+' => 'Overview', + 'Menu:NewIncident' => 'New Incident', + 'Menu:NewIncident+' => 'Create a new Incident ticket', + 'Menu:SearchIncidents' => 'Search for Incidents', + 'Menu:SearchIncidents+' => 'Search for Incident tickets', + 'Menu:Incident:Shortcuts' => 'Shortcuts', + 'Menu:Incident:Shortcuts+' => '', 'Menu:Incident:MyIncidents' => 'Incidents assigned to me', 'Menu:Incident:MyIncidents+' => 'Incidents assigned to me (as Agent)', 'Menu:Incident:EscalatedIncidents' => 'Escalated Incidents', diff --git a/modules/itop-incident-mgmt-1.0.0/es_cr.dict.itop-incident-mgmt.php b/modules/itop-incident-mgmt-1.0.0/es_cr.dict.itop-incident-mgmt.php index abe09149f..d16e982af 100644 --- a/modules/itop-incident-mgmt-1.0.0/es_cr.dict.itop-incident-mgmt.php +++ b/modules/itop-incident-mgmt-1.0.0/es_cr.dict.itop-incident-mgmt.php @@ -28,6 +28,12 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( 'Menu:IncidentManagement+' => 'Gestión de Incidentes', 'Menu:Incident:Overview' => 'Visión General', 'Menu:Incident:Overview+' => 'Visión General', + 'Menu:NewIncident' => 'New Incident', + 'Menu:NewIncident+' => 'Create a new Incident ticket', + 'Menu:SearchIncidents' => 'Search for Incidents', + 'Menu:SearchIncidents+' => 'Search for Incident tickets', + 'Menu:Incident:Shortcuts' => 'Shortcuts', + 'Menu:Incident:Shortcuts+' => '', 'Menu:Incident:MyIncidents' => 'Incidentes asignados a mí', 'Menu:Incident:MyIncidents+' => 'Incidentes asignados a mí (como Agente)', 'Menu:Incident:EscalatedIncidents' => 'Incidentes Escalados', diff --git a/modules/itop-incident-mgmt-1.0.0/fr.dict.itop-incident-mgmt.php b/modules/itop-incident-mgmt-1.0.0/fr.dict.itop-incident-mgmt.php index 2ea56673b..831c4efb6 100644 --- a/modules/itop-incident-mgmt-1.0.0/fr.dict.itop-incident-mgmt.php +++ b/modules/itop-incident-mgmt-1.0.0/fr.dict.itop-incident-mgmt.php @@ -28,6 +28,12 @@ Dict::Add('FR FR', 'French', 'Français', array( 'Menu:IncidentManagement+' => 'Gestion des incidents', 'Menu:Incident:Overview' => 'Vue d\'ensemble', 'Menu:Incident:Overview+' => 'Vue d\'ensemble', + 'Menu:NewIncident' => 'Nouvel Incident', + 'Menu:NewIncident+' => 'Créer un nouveau ticket d\'incident', + 'Menu:SearchIncidents' => 'Rechercher des incidents', + 'Menu:SearchIncidents+' => 'Rechercher parmi les tickets d\'incidents', + 'Menu:Incident:Shortcuts' => 'Raccourcis', + 'Menu:Incident:Shortcuts+' => '', 'Menu:Incident:MyIncidents' => 'Mes tickets', 'Menu:Incident:MyIncidents+' => 'Tickets d\'incident qui me sont assignés', 'Menu:Incident:EscalatedIncidents' => 'Ticket en cours d\'escalade', diff --git a/modules/itop-incident-mgmt-1.0.0/model.itop-incident-mgmt.php b/modules/itop-incident-mgmt-1.0.0/model.itop-incident-mgmt.php index d62989f9b..2db0f4a15 100644 --- a/modules/itop-incident-mgmt-1.0.0/model.itop-incident-mgmt.php +++ b/modules/itop-incident-mgmt-1.0.0/model.itop-incident-mgmt.php @@ -96,8 +96,11 @@ class Incident extends ResponseTicket $oMyMenuGroup = new MenuGroup('IncidentManagement', 40 /* fRank */); new TemplateMenuNode('Incident:Overview', '../modules/itop-incident-mgmt-1.0.0/overview.html', $oMyMenuGroup->GetIndex() /* oParent */, 0 /* fRank */); -new OQLMenuNode('Incident:MyIncidents', 'SELECT Incident WHERE agent_id = :current_contact_id', $oMyMenuGroup->GetIndex(), 1 /* fRank */); -new OQLMenuNode('Incident:EscalatedIncidents', 'SELECT Incident WHERE status IN ("escalated_tto", "escalated_ttr")', $oMyMenuGroup->GetIndex(), 2 /* fRank */); -new OQLMenuNode('Incident:OpenIncidents', 'SELECT Incident WHERE status IN ("new", "assigned", "escalated_tto", "escalated_ttr", "resolved")', $oMyMenuGroup->GetIndex(), 3 /* fRank */); +new NewObjectMenuNode('NewIncident', 'Incident', $oMyMenuGroup->GetIndex(), 1 /* fRank */); +new SearchMenuNode('SearchIncidents', 'Incident', $oMyMenuGroup->GetIndex(), 2 /* fRank */); +$oShortcutNode = new TemplateMenuNode('Incident:Shortcuts', '', $oMyMenuGroup->GetIndex(), 3 /* fRank */); +new OQLMenuNode('Incident:MyIncidents', 'SELECT Incident WHERE agent_id = :current_contact_id', $oShortcutNode->GetIndex(), 1 /* fRank */); +new OQLMenuNode('Incident:EscalatedIncidents', 'SELECT Incident WHERE status IN ("escalated_tto", "escalated_ttr")', $oShortcutNode->GetIndex(), 2 /* fRank */); +new OQLMenuNode('Incident:OpenIncidents', 'SELECT Incident WHERE status IN ("new", "assigned", "escalated_tto", "escalated_ttr", "resolved")', $oShortcutNode->GetIndex(), 3 /* fRank */); ?> diff --git a/modules/itop-request-mgmt-1.0.0/en.dict.itop-request-mgmt.php b/modules/itop-request-mgmt-1.0.0/en.dict.itop-request-mgmt.php index 72631ce4e..e62a91605 100644 --- a/modules/itop-request-mgmt-1.0.0/en.dict.itop-request-mgmt.php +++ b/modules/itop-request-mgmt-1.0.0/en.dict.itop-request-mgmt.php @@ -28,6 +28,12 @@ Dict::Add('EN US', 'English', 'English', array( 'Menu:RequestManagement+' => 'Helpdesk', 'Menu:UserRequest:Overview' => 'Overview', 'Menu:UserRequest:Overview+' => 'Overview', + 'Menu:NewUserRequest' => 'New User Request', + 'Menu:NewUserRequest+' => 'Create a new User Request ticket', + 'Menu:SearchUserRequests' => 'Search for User Requests', + 'Menu:SearchUserRequests+' => 'Search for User Request tickets', + 'Menu:UserRequest:Shortcuts' => 'Shortcuts', + 'Menu:UserRequest:Shortcuts+' => '', 'Menu:UserRequest:MyRequests' => 'Requests assigned to me', 'Menu:UserRequest:MyRequests+' => 'Requests assigned to me (as Agent)', 'Menu:UserRequest:EscalatedRequests' => 'Escalated Requests', diff --git a/modules/itop-request-mgmt-1.0.0/es_cr.dict.itop-request-mgmt.php b/modules/itop-request-mgmt-1.0.0/es_cr.dict.itop-request-mgmt.php index c55e62d6a..b8d5c1a48 100644 --- a/modules/itop-request-mgmt-1.0.0/es_cr.dict.itop-request-mgmt.php +++ b/modules/itop-request-mgmt-1.0.0/es_cr.dict.itop-request-mgmt.php @@ -28,6 +28,12 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( 'Menu:RequestManagement+' => 'Servicio de ayuda', 'Menu:UserRequest:Overview' => 'Visión General', 'Menu:UserRequest:Overview+' => 'Visión General', + 'Menu:NewUserRequest' => 'New User Request', + 'Menu:NewUserRequest+' => 'Create a new User Request ticket', + 'Menu:SearchUserRequests' => 'Search for User Requests', + 'Menu:SearchUserRequests+' => 'Search for User Request tickets', + 'Menu:UserRequest:Shortcuts' => 'Shortcuts', + 'Menu:UserRequest:Shortcuts+' => '', 'Menu:UserRequest:MyRequests' => 'Solicitudes asignadas a mí', 'Menu:UserRequest:MyRequests+' => 'Solicitudes asignadas a mí (como Agente)', 'Menu:UserRequest:EscalatedRequests' => 'Solicitudes Escaladas', diff --git a/modules/itop-request-mgmt-1.0.0/fr.dict.itop-request-mgmt.php b/modules/itop-request-mgmt-1.0.0/fr.dict.itop-request-mgmt.php index de8db84b4..9f63ff409 100644 --- a/modules/itop-request-mgmt-1.0.0/fr.dict.itop-request-mgmt.php +++ b/modules/itop-request-mgmt-1.0.0/fr.dict.itop-request-mgmt.php @@ -28,6 +28,12 @@ Dict::Add('FR FR', 'French', 'Français', array( 'Menu:RequestManagement+' => 'Gestion des demandes utilisateurs', 'Menu:UserRequest:Overview' => 'Vue d\'ensemble', 'Menu:UserRequest:Overview+' => 'Vue d\'ensemble des demandes utilisateurs', + 'Menu:NewUserRequest' => 'Nouvelle demande utilisateur', + 'Menu:NewUserRequest+' => 'Créer un nouveau ticket de demande utilisateur', + 'Menu:SearchUserRequests' => 'Rechercher des demandes utilisateur', + 'Menu:SearchUserRequests+' => 'Rechercher parmi les demandes utilisateur', + 'Menu:UserRequest:Shortcuts' => 'Raccourcis', + 'Menu:UserRequest:Shortcuts+' => '', 'Menu:UserRequest:MyRequests' => 'Mes demandes', 'Menu:UserRequest:MyRequests+' => 'Demandes utilisateurs qui me sont assignées', 'Menu:UserRequest:EscalatedRequests' => 'Demandes en escalade', diff --git a/modules/itop-request-mgmt-1.0.0/model.itop-request-mgmt.php b/modules/itop-request-mgmt-1.0.0/model.itop-request-mgmt.php index 08bec2255..8f6602881 100644 --- a/modules/itop-request-mgmt-1.0.0/model.itop-request-mgmt.php +++ b/modules/itop-request-mgmt-1.0.0/model.itop-request-mgmt.php @@ -86,8 +86,11 @@ class UserRequest extends ResponseTicket $oMyMenuGroup = new MenuGroup('RequestManagement', 30 /* fRank */); new TemplateMenuNode('UserRequest:Overview', '../modules/itop-request-mgmt-1.0.0/overview.html', $oMyMenuGroup->GetIndex() /* oParent */, 0 /* fRank */); -new OQLMenuNode('UserRequest:MyRequests', 'SELECT UserRequest WHERE agent_id = :current_contact_id', $oMyMenuGroup->GetIndex(), 1 /* fRank */); -new OQLMenuNode('UserRequest:EscalatedRequests', 'SELECT UserRequest WHERE status IN ("escalated_tto", "escalated_ttr")', $oMyMenuGroup->GetIndex(), 2 /* fRank */); -new OQLMenuNode('UserRequest:OpenRequests', 'SELECT UserRequest WHERE status IN ("new", "assigned", "escalated_tto", "escalated_ttr", "frozen", "resolved")', $oMyMenuGroup->GetIndex(), 3 /* fRank */); +new NewObjectMenuNode('NewUserRequest', 'UserRequest', $oMyMenuGroup->GetIndex(), 1 /* fRank */); +new SearchMenuNode('SearchUserRequests', 'UserRequest', $oMyMenuGroup->GetIndex(), 2 /* fRank */); +$oShortcutNode = new TemplateMenuNode('UserRequest:Shortcuts', '', $oMyMenuGroup->GetIndex(), 3 /* fRank */); +new OQLMenuNode('UserRequest:MyRequests', 'SELECT UserRequest WHERE agent_id = :current_contact_id', $oShortcutNode->GetIndex(), 1 /* fRank */); +new OQLMenuNode('UserRequest:EscalatedRequests', 'SELECT UserRequest WHERE status IN ("escalated_tto", "escalated_ttr")', $oShortcutNode->GetIndex(), 2 /* fRank */); +new OQLMenuNode('UserRequest:OpenRequests', 'SELECT UserRequest WHERE status IN ("new", "assigned", "escalated_tto", "escalated_ttr", "frozen", "resolved")', $oShortcutNode->GetIndex(), 3 /* fRank */); ?> diff --git a/pages/UI.php b/pages/UI.php index a246d632e..116223594 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -407,7 +407,7 @@ function UpdateObject(&$oObj) { // Non-visible, or read-only attribute, do nothing } - else if ($oAttDef->GetEditClass() == 'Document') + elseif ($oAttDef->GetEditClass() == 'Document') { // There should be an uploaded file with the named attr_ $oDocument = utils::ReadPostedDocument('file_'.$sAttCode); @@ -417,6 +417,17 @@ function UpdateObject(&$oObj) $oObj->Set($sAttCode, $oDocument); } } + elseif ($oAttDef->GetEditClass() == 'One Way Password') + { + // Check if the password was typed/changed + $bChanged = utils::ReadPostedParam("attr_{$sAttCode}_changed", false); + if ($bChanged) + { + // The password has been changed or set + $rawValue = utils::ReadPostedParam("attr_$sAttCode", null); + $oObj->Set($sAttCode, $rawValue); + } + } else { $rawValue = utils::ReadPostedParam("attr_$sAttCode", null); @@ -769,7 +780,7 @@ try { foreach($aSubClasses as $sCandidateClass) { - if (!MetaModel::IsAbstract($sCandidateClass)) + if (!MetaModel::IsAbstract($sCandidateClass) && (UserRights::IsActionAllowed($sCandidateClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES)) { $aPossibleClasses[$sCandidateClass] = MetaModel::GetName($sCandidateClass); }