Kaynağa Gözat

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@708 a333f486-631f-4898-b8df-5754b55c2be0

dflaven 14 yıl önce
ebeveyn
işleme
ba4d40f9e0
34 değiştirilmiş dosya ile 1161 ekleme ve 87 silme
  1. 12 1
      application/cmdbabstract.class.inc.php
  2. 136 23
      application/menunode.class.inc.php
  3. 1 1
      application/template.class.inc.php
  4. 68 0
      application/ui.passwordwidget.class.inc.php
  5. 197 2
      core/attributedef.class.inc.php
  6. 112 8
      core/cmdbchangeop.class.inc.php
  7. 41 1
      core/cmdbobject.class.inc.php
  8. 20 0
      core/config.class.inc.php
  9. 5 0
      core/metamodel.class.php
  10. 119 0
      core/ormpassword.class.inc.php
  11. 235 0
      core/simplecrypt.class.inc.php
  12. 9 0
      dictionaries/dictionary.itop.core.php
  13. 6 2
      dictionaries/dictionary.itop.ui.php
  14. 7 0
      dictionaries/es_cr.dictionary.itop.core.php
  15. 4 1
      dictionaries/es_cr.dictionary.itop.ui.php
  16. 32 23
      dictionaries/fr.dictionary.itop.core.php
  17. 3 0
      dictionaries/fr.dictionary.itop.ui.php
  18. 42 0
      js/forms-json-utils.js
  19. 9 3
      modules/authent-local/model.authent-local.php
  20. 6 0
      modules/itop-change-mgmt-1.0.0/en.dict.itop-change-mgmt.php
  21. 6 0
      modules/itop-change-mgmt-1.0.0/es_cr.dict.itop-change-mgmt.php
  22. 6 0
      modules/itop-change-mgmt-1.0.0/fr.dict.itop-change-mgmt.php
  23. 7 4
      modules/itop-change-mgmt-1.0.0/model.itop-change-mgmt.php
  24. 4 1
      modules/itop-config-mgmt-1.0.0/fr.dict.itop-config-mgmt.php
  25. 13 9
      modules/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php
  26. 6 0
      modules/itop-incident-mgmt-1.0.0/en.dict.itop-incident-mgmt.php
  27. 6 0
      modules/itop-incident-mgmt-1.0.0/es_cr.dict.itop-incident-mgmt.php
  28. 6 0
      modules/itop-incident-mgmt-1.0.0/fr.dict.itop-incident-mgmt.php
  29. 6 3
      modules/itop-incident-mgmt-1.0.0/model.itop-incident-mgmt.php
  30. 6 0
      modules/itop-request-mgmt-1.0.0/en.dict.itop-request-mgmt.php
  31. 6 0
      modules/itop-request-mgmt-1.0.0/es_cr.dict.itop-request-mgmt.php
  32. 6 0
      modules/itop-request-mgmt-1.0.0/fr.dict.itop-request-mgmt.php
  33. 6 3
      modules/itop-request-mgmt-1.0.0/model.itop-request-mgmt.php
  34. 13 2
      pages/UI.php

+ 12 - 1
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)
 			{

+ 136 - 23
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('<li'.$sCSSClass.'><a href="'.$oMenu->GetHyperlink($aExtraParams).'">'.$oMenu->GetTitle().'</a></li>');
-			}
-			else
-			{
-				$oPage->AddToMenu('<li'.$sCSSClass.'>'.$oMenu->GetTitle().'</li>');
-			}
-			$aCurrentMenu = self::$aMenusIndex[$index];
-			if ($iActiveMenu == $index)
-			{
-				$bActive = true;
-			}
-			if (count($aChildren) > 0)
-			{
-				$oPage->AddToMenu('<ul>');
-				$bActive |= self::DisplaySubMenu($oPage, $aChildren, $aExtraParams, $iActiveMenu);
-				$oPage->AddToMenu('</ul>');
+				$aChildren = self::GetChildren($index);
+				$sCSSClass = (count($aChildren) > 0) ? ' class="submenu"' : '';
+				$sHyperlink = $oMenu->GetHyperlink($aExtraParams);
+				if ($sHyperlink != '')
+				{
+					$oPage->AddToMenu('<li'.$sCSSClass.'><a href="'.$oMenu->GetHyperlink($aExtraParams).'">'.$oMenu->GetTitle().'</a></li>');
+				}
+				else
+				{
+					$oPage->AddToMenu('<li'.$sCSSClass.'>'.$oMenu->GetTitle().'</li>');
+				}
+				$aCurrentMenu = self::$aMenusIndex[$index];
+				if ($iActiveMenu == $index)
+				{
+					$bActive = true;
+				}
+				if (count($aChildren) > 0)
+				{
+					$oPage->AddToMenu('<ul>');
+					$bActive |= self::DisplaySubMenu($oPage, $aChildren, $aExtraParams, $iActiveMenu);
+					$oPage->AddToMenu('</ul>');
+				}
 			}
 		}
 		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 = <<<EOF
+		$sTemplate = '';
+
+		if ($this->bSearch)
+		{
+			$sTemplate .= <<<EOF
 <itopblock BlockClass="DisplayBlock" type="search" asynchronous="false" encoding="text/oql">$this->sOQL</itopblock>
+EOF;
+		}
+		
+		$sTemplate .= <<<EOF
 <p class="page-header">$sIcon<itopstring>$this->sPageTitle</itopstring></p>
 <itopblock BlockClass="DisplayBlock" type="list" asynchronous="false" encoding="text/oql">$this->sOQL</itopblock>
 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 = <<<EOF
+<itopblock BlockClass="DisplayBlock" type="search" asynchronous="false" encoding="text/oql" parameters="open:true">SELECT $this->sClass</itopblock>
+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
+	}
+}
 ?>

+ 1 - 1
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

+ 68 - 0
application/ui.passwordwidget.class.inc.php

@@ -0,0 +1,68 @@
+<?php
+// Copyright (C) 2010 Combodo SARL
+//
+//   This program is free software; you can redistribute it and/or modify
+//   it under the terms of the GNU General Public License as published by
+//   the Free Software Foundation; version 3 of the License.
+//
+//   This program is distributed in the hope that it will be useful,
+//   but WITHOUT ANY WARRANTY; without even the implied warranty of
+//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//   GNU General Public License for more details.
+//
+//   You should have received a copy of the GNU General Public License
+//   along with this program; if not, write to the Free Software
+//   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+/**
+ * Class UIPasswordWidget
+ * UI wdiget for displaying and editing one-way encrypted passwords
+ *
+ * @author      Erwan Taloc <erwan.taloc@combodo.com>
+ * @author      Romain Quetiez <romain.quetiez@combodo.com>
+ * @author      Denis Flaven <denis.flaven@combodo.com>
+ * @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 = '<input type="password" name="attr_'.$sCode.'" id="'.$this->iId.'" value="*****"/>&nbsp;<span id="v_'.$this->iId.'"></span><br/>';
+		$sHtmlValue .= '<input type="password" id="'.$this->iId.'_confirm" value="*****"/> '.Dict::S('UI:PasswordConfirm').' <input type="button" value="'.Dict::S('UI:Button:ResetPassword').'" onClick="ResetPwd(\''.$this->iId.'\');">';
+		$sHtmlValue .= '<input type="hidden" id="'.$this->iId.'_changed" name="attr_'.$sCode.'_changed" value="0"/>';
+
+		$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;
+	}
+}
+?>

+ 197 - 2
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"));
@@ -862,6 +863,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 
  *
  * @package     iTopORM
@@ -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 "<pre>\n";
+			print_r($value);
+			echo "</pre>\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

+ 112 - 8
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 .= "<br/>".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 = '<div>'.$this->GetAsHtml('prevdata').'</div>';
 
 			//$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;
 	}

+ 41 - 1
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");

+ 20 - 0
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");

+ 5 - 0
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())
 	{

+ 119 - 0
core/ormpassword.class.inc.php

@@ -0,0 +1,119 @@
+<?php
+// Copyright (C) 2010 Combodo SARL
+//
+//   This program is free software; you can redistribute it and/or modify
+//   it under the terms of the GNU General Public License as published by
+//   the Free Software Foundation; version 3 of the License.
+//
+//   This program is distributed in the hope that it will be useful,
+//   but WITHOUT ANY WARRANTY; without even the implied warranty of
+//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//   GNU General Public License for more details.
+//
+//   You should have received a copy of the GNU General Public License
+//   along with this program; if not, write to the Free Software
+//   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+require_once('../core/simplecrypt.class.inc.php');
+
+/**
+ * ormPassword
+ * encapsulate the behavior of a one way encrypted password stored hashed
+ * with a per password (as random as possible) salt, in order to prevent a "Rainbow table" hack.
+ * If a cryptographic random number generator is available (on Linux or Windows)
+ * it will be used for generating the salt.
+ *
+ * @author      Erwan Taloc <erwan.taloc@combodo.com>
+ * @author      Romain Quetiez <romain.quetiez@combodo.com>
+ * @author      Denis Flaven <denis.flaven@combodo.com>
+ * @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);
+	}
+}
+?>

+ 235 - 0
core/simplecrypt.class.inc.php

@@ -0,0 +1,235 @@
+<?php
+// Copyright (C) 2010 Combodo SARL
+//
+//   This program is free software; you can redistribute it and/or modify
+//   it under the terms of the GNU General Public License as published by
+//   the Free Software Foundation; version 3 of the License.
+//
+//   This program is distributed in the hope that it will be useful,
+//   but WITHOUT ANY WARRANTY; without even the implied warranty of
+//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//   GNU General Public License for more details.
+//
+//   You should have received a copy of the GNU General Public License
+//   along with this program; if not, write to the Free Software
+//   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+/**
+ * SimpleCrypt Class - crypto helpers
+ * Simple encryption of strings, uses mcrypt or degrades to a pure PHP
+ * implementation when mcrypt is not present.
+ * Based on Miguel Ros' work found at:
+ * http://rossoft.wordpress.com/2006/05/22/simple-encryption-class/
+ *
+ * Usage:
+ * $oSimpleCrypt = new SimpleCrypt();
+ * $encrypted = $oSimpleCrypt->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 <rossoft@gmail.com>
+ * @author Erwan Taloc <erwan.taloc@combodo.com>
+ * @author Romain Quetiez <romain.quetiez@combodo.com>
+ * @author Denis Flaven <denis.flaven@combodo.com>
+ * @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<br/>\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<br/>\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<br/>\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);
+    }
+}
+?>

+ 9 - 0
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

+ 6 - 2
dictionaries/dictionary.itop.ui.php

@@ -315,7 +315,7 @@ Dict::Add('EN US', 'English', 'English', array(
 </ul>
 </p>',
 
-	'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)',
 ));
 
 

+ 7 - 0
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

+ 4 - 1
dictionaries/es_cr.dictionary.itop.ui.php

@@ -331,7 +331,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
 </ul>
 </p>',
 
-	'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)',
 ));
 
 ?>

+ 32 - 23
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',

+ 3 - 0
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)',
 ));
 
 ?>

+ 42 - 0
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('<img src="../images/validation_error.png" />');
+			return false;
+		}
+	}
+	$('#v_'+id).html(''); //<img src="../images/validation_ok.png" />');
+	return true;
+}

+ 9 - 3
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;
 		}

+ 6 - 0
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',

+ 6 - 0
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',

+ 6 - 0
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',

+ 7 - 4
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 */);
 
 ?>

+ 4 - 1
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...',
 ));
 
 

+ 13 - 9
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 */);

+ 6 - 0
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',

+ 6 - 0
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',

+ 6 - 0
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',

+ 6 - 3
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 */);
 
 ?>

+ 6 - 0
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',

+ 6 - 0
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',

+ 6 - 0
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',

+ 6 - 3
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 */);
 
 ?>

+ 13 - 2
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_<attCode>
 				$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);
 					}