PageRenderTime 55ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/relations/class.atksecurerelation.inc

https://github.com/ibuildingsnl/ATK
PHP | 543 lines | 372 code | 33 blank | 138 comment | 55 complexity | 2ed22cce6fae4acbd8494e3f34084784 MD5 | raw file
Possible License(s): LGPL-2.0, LGPL-2.1, MPL-2.0-no-copyleft-exception, LGPL-3.0
  1. <?php
  2. /**
  3. * This file is part of the Achievo ATK distribution.
  4. * Detailed copyright and licensing information can be found
  5. * in the doc/COPYRIGHT and doc/LICENSE files which should be
  6. * included in the distribution.
  7. *
  8. * @package atk
  9. * @subpackage relations
  10. *
  11. * @copyright (c)2000-2004 Ibuildings.nl BV
  12. * @license http://www.achievo.org/atk/licensing ATK Open Source License
  13. *
  14. */
  15. atkimport("atk.security.encryption.atkencryption");
  16. userelation("atkonetoonerelation");
  17. /**
  18. * Relationship that can link 2 tables based on a secure link
  19. * that can not be decrypted when not logged in through an atk
  20. * application.
  21. * This effectively secures the database so that data in two
  22. * tables can not be correlated by mischievous access to the database.
  23. *
  24. * @author Mark Baaijens <mark@ibuildings.nl>
  25. *
  26. * @package atk
  27. * @subpackage relations
  28. *
  29. */
  30. class atkSecureRelation extends atkOneToOneRelation
  31. {
  32. var $m_crypt = NULL;
  33. var $m_linktable;
  34. var $m_linkfield;
  35. var $m_linkuserfield = "username";
  36. var $m_keylength;
  37. var $m_searching = false;
  38. var $m_keylookup = array();
  39. var $m_records = array();
  40. var $m_searcharray=array();
  41. var $m_linkpass;
  42. var $m_linkbackfield;
  43. var $m_ownersearch;
  44. var $m_destsearch;
  45. var $m_cachefield;
  46. /**
  47. * Creates an atkSecureRelation,
  48. * similar to an atkOneToOne relation only encrypted
  49. *
  50. * @param string $name The unique name of the attribute. In slave
  51. * mode, this corresponds to the foreign key
  52. * field in the database table.
  53. * @param string $destination The destination node (in module.nodename
  54. * notation)
  55. * @param string $linktable The table we link to
  56. * @param string $linkfield The field we link to
  57. * @param string $linkbackfield
  58. * @param int $keylength The length of the encryption key
  59. * @param string $refKey="" In master mode, this specifies the foreign
  60. * key field from the destination node that
  61. * points to the master record. In slave mode,
  62. * this parameter should be empty.
  63. * @param string $encryption The encryption to use
  64. * @param int $flags Attribute flags that influence this
  65. * attributes' behavior.
  66. */
  67. function atkSecureRelation($name, $destination, $linktable, $linkfield, $linkbackfield, $keylength, $refKey="", $encryption, $flags=0)
  68. {
  69. $this->atkOneToOneRelation($name, $destination, $refKey, $flags|AF_ONETOONE_ERROR);
  70. $this->createDestination();
  71. $this->m_crypt = &atkEncryption::getEncryption($encryption);
  72. $this->m_linktable = $linktable;
  73. $this->m_linkfield = $linkfield;
  74. $this->m_keylength = $keylength;
  75. $this->m_linkbackfield = $linkbackfield;
  76. }
  77. /**
  78. * Set the name of the cache field
  79. *
  80. * @param string $fieldname The cache fieldname
  81. */
  82. function setCacheField($fieldname="cache")
  83. {
  84. $this->m_cachefield = $fieldname;
  85. }
  86. /**
  87. * Adds the attribute / field to the list header. This includes the column name and search field.
  88. *
  89. * Framework method. It should not be necessary to call this method directly.
  90. *
  91. * @param String $action the action that is being performed on the node
  92. * @param array $arr reference to the the recordlist array
  93. * @param String $fieldprefix the fieldprefix
  94. * @param int $flags the recordlist flags
  95. * @param array $atksearch the current ATK search list (if not empty)
  96. * @param String $atkorderby the current ATK orderby string (if not empty)
  97. */
  98. function addToListArrayHeader($action, &$arr, $fieldprefix, $flags, $atksearch, $atkorderby)
  99. {
  100. if ($this->hasFlag(AF_ONETOONE_INTEGRATE))
  101. {
  102. // integrated version, don't add ourselves, but add all columns from the destination.
  103. if ($this->createDestination())
  104. {
  105. foreach (array_keys($this->m_destInstance->m_attribList) as $attribname)
  106. {
  107. $p_attrib = &$this->m_destInstance->getAttribute($attribname);
  108. $p_attrib->addFlag(AF_NO_SORT);
  109. }
  110. }
  111. }
  112. parent::addToListArrayHeader($action, $arr, $fieldprefix, $flags, $atksearch, $atkorderby);
  113. }
  114. /**
  115. * Gets the password for the link
  116. * for more security the administrator gets a random password. You have to capture in your application that
  117. * the administrator is only able to insert the first record in this relation and make also a useracount with it.
  118. * @param string $linktable the table where we find the linkpass
  119. * @param string $linkfield the field where we find the encrypted linkpass
  120. * @param string $encryption The encryption to use
  121. * @return string the password for the link
  122. */
  123. function getLinkPassword($linktable,$linkfield,$encryption="")
  124. {
  125. if ($this->m_linkpass) return $this->m_linkpass;
  126. if (!$linktable) $linktable = $this->m_linktable;
  127. if (!$linkfield) $linkfield = $this->m_linkfield;
  128. $user = getUser();
  129. $username = $user['name'];
  130. $password = $user['PASS'];
  131. if($encryption)
  132. $crypt = atkEncryption::getEncryption($encryption);
  133. else
  134. $crypt = $this->m_crypt;
  135. if($username == "administrator")
  136. {
  137. //if the administrator asks for a password we generate one
  138. //because the administrator only makes the first person
  139. global $linkpass;
  140. if(!$linkpass)
  141. $linkpass = $crypt->getRandomKey($password);
  142. }
  143. else
  144. {
  145. $query = "SELECT ".$linkfield." as pass FROM ".$linktable." WHERE ".atkconfig("auth_userfield")." = '".$username."'";
  146. $db = &atkGetDb();
  147. $rec = $db->getrows($query);
  148. if(count($rec) < 1)
  149. return $linkpass;
  150. $encryptedpass = array_pop($rec);
  151. $linkpass = $encryptedpass['pass'];
  152. }
  153. $this->m_linkpass = $crypt->decryptKey($linkpass,$password);
  154. return $this->m_linkpass;
  155. }
  156. /**
  157. * This function in the atkOneToOneRelation store the record of the parentnode in the DB
  158. * with the reference key of the other table.
  159. * So we encrypt the reference key before we call the method.
  160. * For more documentation see the atkOneToOneRelation
  161. *
  162. * @param atkQuery $query The SQL query object
  163. * @param String $tablename The name of the table of this attribute
  164. * @param String $fieldaliasprefix Prefix to use in front of the alias
  165. * in the query.
  166. * @param Array $rec The record that contains the value of this attribute.
  167. * @param int $level Recursion level if relations point to eachother, an
  168. * endless loop could occur if they keep loading
  169. * eachothers data. The $level is used to detect this
  170. * loop. If overriden in a derived class, any subcall to
  171. * an addToQuery method should pass the $level+1.
  172. * @param String $mode Indicates what kind of query is being processing:
  173. * This can be any action performed on a node (edit,
  174. * add, etc) Mind you that "add" and "update" are the
  175. * actions that store something in the database,
  176. * whereas the rest are probably select queries.
  177. */
  178. function addToQuery(&$query, $tablename="", $fieldaliasprefix="", $rec="", $level=0, $mode="")
  179. {
  180. $records = $this->m_records;
  181. if (count($records)==0 && !$this->m_searching)
  182. {
  183. if(is_array($rec))
  184. {
  185. $link = $rec[$this->fieldName()][$this->m_destInstance->m_primaryKey[0]];
  186. $cryptedlink = $this->m_crypt->encrypt($link,$this->getLinkPassword($this->m_linktable, $this->m_linkfield));
  187. $rec[$this->fieldName()][$this->m_destInstance->m_primaryKey[0]] = addslashes($cryptedlink);
  188. }
  189. return parent::addToQuery($query, $tablename, $fieldaliasprefix, $rec, $level, $mode);
  190. }
  191. else // lookup matching
  192. {
  193. $where = array();
  194. foreach (array_keys($this->m_keylookup) as $decryptedlink)
  195. {
  196. $where[] = $decryptedlink;
  197. }
  198. if ($tablename) $tablename .= ".";
  199. $query->addSearchCondition($tablename.$this->m_ownerInstance->primaryKeyField()." IN ('".implode("','", $where)."')");
  200. }
  201. }
  202. /**
  203. * This function in the atkOneToOneRelation loads the record of the childnode from the DB
  204. * with the the id from de reference key in childnode.
  205. * So we decrypt the reference key before we call the method.
  206. * For more documentation see the atkOneToOneRelation
  207. *
  208. * @param atkDb $db The database object
  209. * @param array $record The record
  210. * @param string $mode The mode we're in ("add", "edit", "copy")
  211. * @return array The loaded records
  212. */
  213. function load(&$db, $record, $mode)
  214. {
  215. if ($this->m_searching)
  216. {
  217. if($this->m_searcharray !== $this->m_ownerInstance->m_postvars["atksearch"][$this->fieldName()]
  218. && is_array($this->m_searcharray))
  219. {
  220. $this->m_records = array();
  221. $this->m_keylookup= array();
  222. // perform query on destination node to retrieve all records.
  223. if ($this->createDestination())
  224. {
  225. $this->m_searcharray = $this->m_ownerInstance->m_postvars["atksearch"][$this->fieldName()];
  226. //if al search values are equal, then make it an OR search
  227. if(count(array_unique(array_values($this->m_searcharray))) == 1)
  228. $this->m_destInstance->m_postvars['atksearchmethod'] = "OR";
  229. $oldsearcharray = $this->m_searcharray;
  230. // check wether mentioned fields are actually in the node
  231. foreach ($this->m_searcharray as $searchfield => $searchvalue)
  232. {
  233. if (!is_object($this->m_destInstance->m_attribList[$searchfield]))
  234. unset($this->m_searcharray[$searchfield]);
  235. }
  236. $this->m_destInstance->m_postvars["atksearch"] = $this->m_searcharray;
  237. $this->m_destInstance->m_postvars["atksearchmode"] = $this->m_ownerInstance->m_postvars["atksearchmode"];
  238. $this->m_destInstance->m_postvars["atksearchmethod"] = $this->m_ownerInstance->m_postvars["atksearchmethod"];
  239. $records = $this->m_destInstance->selectDb();
  240. $this->m_searcharray = $oldsearcharray;
  241. $errorconfig = atkconfig("securerelation_decrypterror", null);
  242. // create lookup table for easy reference.
  243. for ($i=0, $_i=count($records); $i<$_i; $i++)
  244. {
  245. $decryptedlink = $this->decrypt($records[$i], $this->m_linkbackfield);
  246. if(!$decryptedlink && $errorconfig)
  247. {
  248. atkdebug("Unable to decrypt link: ".$link."for record: ".var_export($records[$i], true)." with linkbackfield: ".$this->m_linkbackfield);
  249. $decrypterror=true;
  250. }
  251. else
  252. {
  253. $this->m_keylookup[$decryptedlink] = $i;
  254. $this->m_records[] = $records[$i];
  255. }
  256. }
  257. if ($decrypterror)
  258. {
  259. if ($errorconfig==2) atkerror("There were errors decrypting the secured links, see debuginfo");
  260. else if ($errorconfig==1) mailreport();
  261. }
  262. return $this->m_records;
  263. }
  264. }
  265. else // lookup table present, postload stage
  266. {
  267. $this->m_searching = false;
  268. return $this->m_records[$this->m_keylookup[$record[$this->m_ownerInstance->primaryKeyField()]]];
  269. }
  270. }
  271. else
  272. {
  273. if (!$record[$this->fieldName()] || (!$record[$this->m_cachefield] && $this->m_cachefield))
  274. {
  275. $query = "SELECT ".$this->fieldName();
  276. $query.= ($this->m_cachefield?",{$this->m_cachefield}":"");
  277. $query.=" FROM ".$this->m_ownerInstance->m_table." WHERE ".$this->m_ownerInstance->m_table.".".$this->m_ownerInstance->primaryKeyField()."='".$record[$this->m_ownerInstance->primaryKeyField()]."'";
  278. $result = $db->getrows($query);
  279. }
  280. else
  281. {
  282. $result[0] = $record;
  283. }
  284. $cryptedlink = $this->decrypt($result[0],$this->fieldName());
  285. $records[0][$this->fieldName()] = $cryptedlink;
  286. if ($cryptedlink)
  287. {
  288. $record[$this->fieldName()] = $cryptedlink;
  289. //for the use of encrypted id's we don't want to use the refkey,
  290. //because in that case we have to encrypt the id of the employee
  291. //and than atk CAN get the destination data, but not the owner data.
  292. //so we backup de refkey, make in empty and put it back after loading the record.
  293. $backup_refkey = $this->m_refKey;
  294. $this->m_refKey = "";
  295. $load = parent::load($db, $record, $mode);
  296. $this->m_refKey = $backup_refkey;
  297. return $load;
  298. }
  299. else
  300. {
  301. atkdebug("Could not decrypt the link: $link for ".$this->m_ownerInstance->primaryKeyField()."='".$record[$this->m_ownerInstance->primaryKeyField()]);
  302. }
  303. }
  304. }
  305. /**
  306. * Decrypt the field
  307. *
  308. * @param array $record
  309. * @param array $field
  310. * @return string The decrypted value
  311. */
  312. function decrypt($record, $field)
  313. {
  314. global $g_encryption;
  315. if (!$this->m_linkpass) $this->getLinkPassword($this->m_linktable, $this->m_linkfield, $g_encryption);
  316. if (!$this->m_cachefield || !$record[$this->m_cachefield])
  317. {
  318. $cryptedlink = $this->m_crypt->decrypt($record[$field],$this->m_linkpass);
  319. if ($this->m_ownerInstance)
  320. {
  321. if ($this->m_cachefield && is_numeric($cryptedlink) && $cryptedlink && $record[$this->m_ownerInstance->primaryKeyField()] && $this->createDestination())
  322. {
  323. if ($this->m_ownerInstance->m_attribList[$field]) $cachetable = $this->m_ownerInstance->m_table;
  324. else if ($this->m_destInstance->m_attribList[$field]) $cachetable = $this->m_destInstance->m_table;
  325. $db = &atkGetDb();
  326. $db->query("UPDATE $cachetable
  327. SET {$this->m_cachefield}='$cryptedlink'
  328. WHERE ".$this->m_ownerInstance->primaryKeyField()." = '".
  329. $record[$this->m_ownerInstance->primaryKeyField()]."'");
  330. }
  331. else if (!$cryptedlink || !is_numeric($cryptedlink))
  332. {
  333. atkdebug("decrypt($record, $field) failed! and yielded: $cryptedlink");
  334. return NULL;
  335. }
  336. }
  337. else
  338. {
  339. halt("no ownerinstance found for the secure relation");
  340. }
  341. }
  342. else
  343. {
  344. $cryptedlink = $record[$this->m_cachefield];
  345. }
  346. return $cryptedlink;
  347. }
  348. /**
  349. * For creating a new user put the linkpassword in the db
  350. * @param string $id the id of the user to create
  351. * @param string $pass the password for the user
  352. */
  353. function newUser($id,$pass)
  354. {
  355. $db = &atkGetDb();
  356. $linkpass = $this->m_crypt->encryptKey($this->getLinkPassword($this->m_linktable,$this->m_linkfield),$pass);
  357. $query = "UPDATE $this->m_linktable SET $this->m_linkfield = '".$linkpass."' WHERE id = '$id'";
  358. $db->query($query);
  359. }
  360. /**
  361. * Returns the condition which can be used when calling atkQuery's addJoin() method
  362. * Joins the relation's owner with the destination
  363. */
  364. function _getJoinCondition()
  365. {
  366. $db = &atkGetDb();
  367. // decrypt the encrypted keys to get the tables joined
  368. $temp_query = "SELECT " . $this->fieldName() . " FROM " . $this->m_ownerInstance->m_table;
  369. $result = $db->getRows($temp_query);
  370. $condition = "";
  371. foreach($result as $recordArray)
  372. {
  373. $record = $recordArray[$this->fieldName()];
  374. $decrypted_record = $this->decrypt($recordArray,$this->fieldName());
  375. if ($condition == "")
  376. $whereOrAnd = "(";
  377. else
  378. $whereOrAnd = "OR";
  379. $condition .= $whereOrAnd . " (" . $this->m_destInstance->m_table . "." . $this->m_destInstance->primaryKeyField() . "='" . $decrypted_record . "' ";
  380. $condition .= "AND " . $this->m_ownerInstance->m_table . "." . $this->fieldName() . "=\"".addslashes($record)."\") ";
  381. }
  382. $condition .= ") ";
  383. return $condition;
  384. }
  385. /**
  386. * Determine the load type of this attribute.
  387. *
  388. * @param String $mode The type of load (view,admin,edit etc)
  389. * @param bool $searching Is this a search?
  390. *
  391. * @return int Bitmask containing information about load requirements.
  392. * POSTLOAD|ADDTOQUERY when AF_ONETOONE_LAZY is set.
  393. * ADDTOQUERY when AF_ONETOONE_LAZY is not set.
  394. */
  395. function loadType($mode, $searching=false)
  396. {
  397. if ($searching)
  398. {
  399. $this->m_searching = true;
  400. return PRELOAD|ADDTOQUERY|POSTLOAD;
  401. }
  402. else
  403. {
  404. return parent::loadType($mode, $searching);
  405. }
  406. }
  407. /**
  408. * Creates a search condition for a given search value, and adds it to the
  409. * query that will be used for performing the actual search.
  410. *
  411. * @param atkQuery $query The query to which the condition will be added.
  412. * @param String $table The name of the table in which this attribute
  413. * is stored
  414. * @param mixed $value The value the user has entered in the searchbox
  415. * @param String $searchmode The searchmode to use. This can be any one
  416. * of the supported modes, as returned by this
  417. * attribute's getSearchModes() method.
  418. * @param string $fieldaliasprefix optional prefix for the fieldalias in the table
  419. */
  420. function searchCondition(&$query, $table, $value, $searchmode, $fieldaliasprefix='')
  421. {
  422. //dummy implementation, we handle our own search in the destination node.
  423. }
  424. /**
  425. * Creates a searchcondition for the field,
  426. * was once part of searchCondition, however,
  427. * searchcondition() also immediately adds the search condition.
  428. *
  429. * @param atkQuery $query The query object where the search condition should be placed on
  430. * @param String $table The name of the table in which this attribute
  431. * is stored
  432. * @param mixed $value The value the user has entered in the searchbox
  433. * @param String $searchmode The searchmode to use. This can be any one
  434. * of the supported modes, as returned by this
  435. * attribute's getSearchModes() method.
  436. * @return String The searchcondition to use.
  437. */
  438. function getSearchCondition(&$query, $table, $value, $searchmode)
  439. {
  440. // Off course, the secure relation has to have a special search condition
  441. // because searching on a secure relation has to be broken up in 2 pieces
  442. // first the destination, then the owner, filtered by the results from
  443. // the search on the destination
  444. $searchConditions = array();
  445. $descfields = $this->m_destInstance->descriptorFields();
  446. $prevdescfield = "";
  447. foreach($descfields as $descField)
  448. {
  449. if ($descField !== $prevdescfield && $descField !== $this->m_owner)
  450. {
  451. $p_attrib = &$this->m_destInstance->getAttribute($descField);
  452. if (is_object($p_attrib))
  453. {
  454. if ($p_attrib->m_destInstance)
  455. {
  456. $itsTable = $p_attrib->m_destInstance->m_table;
  457. }
  458. else
  459. {
  460. $itsTable = $p_attrib->m_ownerInstance->m_table;
  461. }
  462. if (is_array($searchmode)) $searchmode = $searchmode[$this->fieldName()];
  463. if (!$searchmode) $searchmode = atkconfig("search_defaultmode");
  464. // checking for the getSearchCondition
  465. // for backwards compatibility
  466. if (method_exists($p_attrib,"getSearchCondition"))
  467. {
  468. $searchcondition = $p_attrib->getSearchCondition($query,$itsTable,$value,$searchmode);
  469. if ($searchcondition)
  470. {
  471. $searchConditions[] = $searchcondition;
  472. }
  473. }
  474. else
  475. {
  476. $p_attrib->searchCondition($query,$itsTable,$value,$searchmode);
  477. }
  478. }
  479. $prevdescfield = $descField;
  480. }
  481. }
  482. if (!$this->m_destsearch[$value] && $this->createDestination())
  483. {
  484. $this->m_destsearch[$value] = $this->m_destInstance->selectDb(implode(" OR ",$searchConditions));
  485. }
  486. foreach ($this->m_destsearch[$value] as $result)
  487. {
  488. $destresult = $this->decrypt($result,$this->m_linkbackfield);
  489. if ($destresult) $destresults[] = $destresult;
  490. }
  491. if ($query->m_joinaliases[$this->m_ownerInstance->m_table."*".$this->m_ownerInstance->primaryKeyField()]) $table = $query->m_joinaliases[$this->m_ownerInstance->m_table."*".$this->m_ownerInstance->primaryKeyField()];
  492. else if (in_array($this->m_ownerInstance->m_table, $query->m_tables)) $table = $this->m_ownerInstance->m_table;
  493. else $table=null;
  494. if (!empty($destresults) && $table)
  495. return $table.".".$this->m_ownerInstance->primaryKeyField()." IN (".implode(",",$destresults).")";
  496. }
  497. }
  498. ?>