Token.php 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. <?php
  2. App::uses('ToolsAppModel', 'Tools.Model');
  3. App::uses('CommonComponent', 'Tools.Controller/Component');
  4. /**
  5. * A generic model to hold tokens
  6. *
  7. * @author Mark Scherer
  8. * @cakephp 2.x
  9. * @license MIT
  10. * 2011-11-17 ms
  11. */
  12. class Token extends ToolsAppModel {
  13. public $displayField = 'key';
  14. public $order = array('Token.created' => 'DESC');
  15. protected $defaultLength = 22;
  16. protected $validity = MONTH;
  17. public $validate = array(
  18. 'type' => array(
  19. 'notEmpty' => array(
  20. 'rule' => array('notEmpty'),
  21. 'message' => 'valErrMandatoryField',
  22. ),
  23. ),
  24. 'key' => array(
  25. 'notEmpty' => array(
  26. 'rule' => array('notEmpty'),
  27. 'message' => 'valErrMandatoryField',
  28. 'last' => true,
  29. ),
  30. 'isUnique' => array(
  31. 'rule' => array('isUnique'),
  32. 'message' => 'valErrTokenExists',
  33. ),
  34. ),
  35. 'content' => array(
  36. 'maxLength' => array(
  37. 'rule' => array('maxLength', 255),
  38. 'message' => array('valErrMaxCharacters %s', 255),
  39. 'allowEmpty' => true
  40. ),
  41. ),
  42. 'used' => array('numeric')
  43. );
  44. //public $types = array('activate');
  45. /**
  46. * stores new key in DB
  47. * @param string type: necessary
  48. * @param string key: optional key, otherwise a key will be generated
  49. * @param mixed user_id: optional (if used, only this user can use this key)
  50. * @param string content: up to 255 characters of content may be added (optional)
  51. * NOW: checks if this key is already used (should be unique in table)
  52. * @return string key on SUCCESS, boolean false otherwise
  53. * 2009-05-13 ms
  54. */
  55. public function newKey($type, $key = null, $uid = null, $content = null) {
  56. if (empty($type)) { // || !in_array($type, $this->types)
  57. return false;
  58. }
  59. if (empty($key)) {
  60. $key = $this->generateKey($this->defaultLength);
  61. $keyLength = $this->defaultLength;
  62. } else {
  63. $keyLength = mb_strlen($key);
  64. }
  65. $data = array(
  66. 'type' => $type,
  67. 'user_id' => (string)$uid,
  68. 'content' => (string)$content,
  69. 'key' => $key,
  70. );
  71. $this->set($data);
  72. $max = 99;
  73. while (!$this->validates()) {
  74. $data['key'] = $this->generateKey($keyLength);
  75. $this->set($data);
  76. $max--;
  77. if ($max === 0) { //die('Exeption in Token');
  78. return false;
  79. }
  80. }
  81. $this->create();
  82. if ($this->save($data)) {
  83. return $key;
  84. }
  85. return false;
  86. }
  87. /**
  88. * usesKey (only once!) - by KEY
  89. * @param string type: necessary
  90. * @param string key: necessary
  91. * @param mixed user_id: needs to be provided if this key has a user_id stored
  92. * @return ARRAY(content) if successfully used or if already used (used=1), FALSE else
  93. * 2009-05-13 ms
  94. */
  95. public function useKey($type, $key, $uid = null, $treatUsedAsInvalid = false) {
  96. if (empty($type) || empty($key)) {
  97. return false;
  98. }
  99. $conditions = array('conditions'=>array($this->alias.'.key'=>$key, $this->alias.'.type'=>$type));
  100. if (!empty($uid)) {
  101. $conditions['conditions'][$this->alias.'.user_id'] = $uid;
  102. }
  103. $res = $this->find('first', $conditions);
  104. if (empty($res)) {
  105. return false;
  106. }
  107. if (!empty($uid) && !empty($res[$this->alias]['user_id']) && $res[$this->alias]['user_id'] != $uid) {
  108. // return $res; # more secure to fail here if user_id is not provided, but was submitted prev.
  109. return false;
  110. }
  111. # already used?
  112. if (!empty($res[$this->alias]['used'])) {
  113. if ($treatUsedAsInvalid) {
  114. return false;
  115. }
  116. # return true and let the application check what to do then
  117. return $res;
  118. }
  119. # actually spend key (set to used)
  120. if ($this->spendKey($res[$this->alias]['id'])) {
  121. return $res;
  122. }
  123. # no limit? we dont spend key then
  124. if (!empty($res[$this->alias]['unlimited'])) {
  125. return $res;
  126. }
  127. $this->log('VIOLATION in '.$this->alias.' Model (method useKey)');
  128. return false;
  129. }
  130. /**
  131. * sets Key to "used" (only once!) - directly by ID
  132. * @param id of key to spend: necessary
  133. * @return boolean true on success, false otherwise
  134. * 2009-05-13 ms
  135. */
  136. public function spendKey($id = null) {
  137. if (empty($id)) {
  138. return false;
  139. }
  140. //$this->id = $id;
  141. if ($this->updateAll(array($this->alias.'.used' => $this->alias.'.used + 1', $this->alias.'.modified'=>'"'.date(FORMAT_DB_DATETIME).'"'), array($this->alias.'.id'=>$id))) {
  142. return true;
  143. }
  144. return false;
  145. }
  146. /**
  147. * remove old/invalid keys
  148. * does not remove recently used ones (for proper feedback)!
  149. * @return boolean success
  150. * 2010-06-17 ms
  151. */
  152. public function garbigeCollector() {
  153. $conditions = array(
  154. $this->alias.'.created <'=>date(FORMAT_DB_DATETIME, time()-$this->validity),
  155. );
  156. return $this->deleteAll($conditions, false);
  157. }
  158. /**
  159. * get admin stats
  160. * 2010-10-22 ms
  161. */
  162. public function stats() {
  163. $keys = array();
  164. $keys['unused_valid'] = $this->find('count', array('conditions'=>array($this->alias.'.used'=>0, $this->alias.'.created >='=>date(FORMAT_DB_DATETIME, time()-$this->validity))));
  165. $keys['used_valid'] = $this->find('count', array('conditions'=>array($this->alias.'.used'=>1, $this->alias.'.created >='=>date(FORMAT_DB_DATETIME, time()-$this->validity))));
  166. $keys['unused_invalid'] = $this->find('count', array('conditions'=>array($this->alias.'.used'=>0, $this->alias.'.created <'=>date(FORMAT_DB_DATETIME, time()-$this->validity))));
  167. $keys['used_invalid'] = $this->find('count', array('conditions'=>array($this->alias.'.used'=>1, $this->alias.'.created <'=>date(FORMAT_DB_DATETIME, time()-$this->validity))));
  168. $types = $this->find('all', array('conditions'=>array(), 'fields'=>array('DISTINCT type')));
  169. $keys['types'] = !empty($types) ? Set::extract('/'.$this->alias.'/type', $types) : array();
  170. return $keys;
  171. }
  172. /**
  173. * Generator
  174. *
  175. * TODO: move functionality into Lib class
  176. *
  177. * @param length (defaults to defaultLength)
  178. * @return string key
  179. * 2009-05-13 ms
  180. */
  181. public function generateKey($length = null) {
  182. if (empty($length)) {
  183. $length = $this->defaultLength;
  184. }
  185. if ((class_exists('CommonComponent') || App::import('Component', 'Common')) && method_exists('CommonComponent', 'generatePassword')) {
  186. return CommonComponent::generatePassword($length);
  187. } else {
  188. return $this->_generateKey($length);
  189. }
  190. }
  191. /**
  192. * backup method - only used if no custom function exists
  193. * 2010-06-17 ms
  194. */
  195. protected function _generateKey($length = null) {
  196. $chars = '234567890abcdefghijkmnopqrstuvwxyz'; // ABCDEFGHIJKLMNOPQRSTUVWXYZ
  197. $i = 0;
  198. $password = '';
  199. $max = strlen($chars) - 1;
  200. while ($i < $length) {
  201. $password .= $chars[mt_rand(0, $max)];
  202. $i++;
  203. }
  204. return $password;
  205. }
  206. }