Joomla Platform  13.1
Documentation des API du framework Joomla Platform
 Tout Classes Espaces de nommage Fichiers Fonctions Variables Pages
helper.php
Aller à la documentation de ce fichier.
1 <?php
2 /**
3  * @package Joomla.Platform
4  * @subpackage User
5  *
6  * @copyright Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.
7  * @license GNU General Public License version 2 or later; see LICENSE
8  */
9 
10 defined('JPATH_PLATFORM') or die;
11 
12 // Always make sure that the password hashing API has been defined.
13 // include_once JPATH_ROOT . '/libraries/compat/password/lib/password.php';
14 
15 /**
16  * Authorisation helper class, provides static methods to perform various tasks relevant
17  * to the Joomla user and authorisation classes
18  *
19  * This class has influences and some method logic from the Horde Auth package
20  *
21  * @package Joomla.Platform
22  * @subpackage User
23  * @since 11.1
24  */
25 abstract class JUserHelper
26 {
27  /**
28  * Method to add a user to a group.
29  *
30  * @param integer $userId The id of the user.
31  * @param integer $groupId The id of the group.
32  *
33  * @return boolean True on success
34  *
35  * @since 11.1
36  * @throws RuntimeException
37  */
38  public static function addUserToGroup($userId, $groupId)
39  {
40  // Get the user object.
41  $user = new JUser((int) $userId);
42 
43  // Add the user to the group if necessary.
44  if (!in_array($groupId, $user->groups))
45  {
46  // Get the title of the group.
47  $db = JFactory::getDbo();
48  $query = $db->getQuery(true)
49  ->select($db->quoteName('title'))
50  ->from($db->quoteName('#__usergroups'))
51  ->where($db->quoteName('id') . ' = ' . (int) $groupId);
52  $db->setQuery($query);
53  $title = $db->loadResult();
54 
55  // If the group does not exist, return an exception.
56  if (!$title)
57  {
58  throw new RuntimeException('Access Usergroup Invalid');
59  }
60 
61  // Add the group data to the user object.
62  $user->groups[$title] = $groupId;
63 
64  // Store the user object.
65  $user->save();
66  }
67 
68  if (session_id())
69  {
70  // Set the group data for any preloaded user objects.
71  $temp = JFactory::getUser((int) $userId);
72  $temp->groups = $user->groups;
73 
74  // Set the group data for the user object in the session.
75  $temp = JFactory::getUser();
76 
77  if ($temp->id == $userId)
78  {
79  $temp->groups = $user->groups;
80  }
81  }
82 
83  return true;
84  }
85 
86  /**
87  * Method to get a list of groups a user is in.
88  *
89  * @param integer $userId The id of the user.
90  *
91  * @return array List of groups
92  *
93  * @since 11.1
94  */
95  public static function getUserGroups($userId)
96  {
97  // Get the user object.
98  $user = JUser::getInstance((int) $userId);
99 
100  return isset($user->groups) ? $user->groups : array();
101  }
102 
103  /**
104  * Method to remove a user from a group.
105  *
106  * @param integer $userId The id of the user.
107  * @param integer $groupId The id of the group.
108  *
109  * @return boolean True on success
110  *
111  * @since 11.1
112  */
113  public static function removeUserFromGroup($userId, $groupId)
114  {
115  // Get the user object.
116  $user = JUser::getInstance((int) $userId);
117 
118  // Remove the user from the group if necessary.
119  $key = array_search($groupId, $user->groups);
120 
121  if ($key !== false)
122  {
123  // Remove the user from the group.
124  unset($user->groups[$key]);
125 
126  // Store the user object.
127  $user->save();
128  }
129 
130  // Set the group data for any preloaded user objects.
131  $temp = JFactory::getUser((int) $userId);
132  $temp->groups = $user->groups;
133 
134  // Set the group data for the user object in the session.
135  $temp = JFactory::getUser();
136 
137  if ($temp->id == $userId)
138  {
139  $temp->groups = $user->groups;
140  }
141 
142  return true;
143  }
144 
145  /**
146  * Method to set the groups for a user.
147  *
148  * @param integer $userId The id of the user.
149  * @param array $groups An array of group ids to put the user in.
150  *
151  * @return boolean True on success
152  *
153  * @since 11.1
154  */
155  public static function setUserGroups($userId, $groups)
156  {
157  // Get the user object.
158  $user = JUser::getInstance((int) $userId);
159 
160  // Set the group ids.
161  JArrayHelper::toInteger($groups);
162  $user->groups = $groups;
163 
164  // Get the titles for the user groups.
165  $db = JFactory::getDbo();
166  $query = $db->getQuery(true)
167  ->select($db->quoteName('id') . ', ' . $db->quoteName('title'))
168  ->from($db->quoteName('#__usergroups'))
169  ->where($db->quoteName('id') . ' = ' . implode(' OR ' . $db->quoteName('id') . ' = ', $user->groups));
170  $db->setQuery($query);
171  $results = $db->loadObjectList();
172 
173  // Set the titles for the user groups.
174  for ($i = 0, $n = count($results); $i < $n; $i++)
175  {
176  $user->groups[$results[$i]->id] = $results[$i]->id;
177  }
178 
179  // Store the user object.
180  $user->save();
181 
182  if (session_id())
183  {
184  // Set the group data for any preloaded user objects.
185  $temp = JFactory::getUser((int) $userId);
186  $temp->groups = $user->groups;
187 
188  // Set the group data for the user object in the session.
189  $temp = JFactory::getUser();
190 
191  if ($temp->id == $userId)
192  {
193  $temp->groups = $user->groups;
194  }
195  }
196 
197  return true;
198  }
199 
200  /**
201  * Gets the user profile information
202  *
203  * @param integer $userId The id of the user.
204  *
205  * @return object
206  *
207  * @since 11.1
208  */
209  public static function getProfile($userId = 0)
210  {
211  if ($userId == 0)
212  {
213  $user = JFactory::getUser();
214  $userId = $user->id;
215  }
216 
217  // Get the dispatcher and load the user's plugins.
218  $dispatcher = JEventDispatcher::getInstance();
219  JPluginHelper::importPlugin('user');
220 
221  $data = new JObject;
222  $data->id = $userId;
223 
224  // Trigger the data preparation event.
225  $dispatcher->trigger('onContentPrepareData', array('com_users.profile', &$data));
226 
227  return $data;
228  }
229 
230  /**
231  * Method to activate a user
232  *
233  * @param string $activation Activation string
234  *
235  * @return boolean True on success
236  *
237  * @since 11.1
238  */
239  public static function activateUser($activation)
240  {
241  $db = JFactory::getDbo();
242 
243  // Let's get the id of the user we want to activate
244  $query = $db->getQuery(true)
245  ->select($db->quoteName('id'))
246  ->from($db->quoteName('#__users'))
247  ->where($db->quoteName('activation') . ' = ' . $db->quote($activation))
248  ->where($db->quoteName('block') . ' = 1')
249  ->where($db->quoteName('lastvisitDate') . ' = ' . $db->quote('0000-00-00 00:00:00'));
250  $db->setQuery($query);
251  $id = (int) $db->loadResult();
252 
253  // Is it a valid user to activate?
254  if ($id)
255  {
256  $user = JUser::getInstance((int) $id);
257 
258  $user->set('block', '0');
259  $user->set('activation', '');
260 
261  // Time to take care of business.... store the user.
262  if (!$user->save())
263  {
264  JLog::add($user->getError(), JLog::WARNING, 'jerror');
265 
266  return false;
267  }
268  }
269  else
270  {
271  JLog::add(JText::_('JLIB_USER_ERROR_UNABLE_TO_FIND_USER'), JLog::WARNING, 'jerror');
272 
273  return false;
274  }
275 
276  return true;
277  }
278 
279  /**
280  * Returns userid if a user exists
281  *
282  * @param string $username The username to search on.
283  *
284  * @return integer The user id or 0 if not found.
285  *
286  * @since 11.1
287  */
288  public static function getUserId($username)
289  {
290  // Initialise some variables
291  $db = JFactory::getDbo();
292  $query = $db->getQuery(true)
293  ->select($db->quoteName('id'))
294  ->from($db->quoteName('#__users'))
295  ->where($db->quoteName('username') . ' = ' . $db->quote($username));
296  $db->setQuery($query, 0, 1);
297 
298  return $db->loadResult();
299  }
300 
301  /**
302  * Formats a password using the current encryption.
303  *
304  * @param string $plaintext The plaintext password to encrypt.
305  * @param string $salt The salt to use to encrypt the password. []
306  * If not present, a new salt will be
307  * generated.
308  * @param string $encryption The kind of password encryption to use.
309  * Defaults to md5-hex.
310  * @param boolean $show_encrypt Some password systems prepend the kind of
311  * encryption to the crypted password ({SHA},
312  * etc). Defaults to false.
313  *
314  * @return string The encrypted password.
315  *
316  * @since 11.1
317  * @note In Joomla! CMS 3.2 the default encrytion has been changed to bcrypt. When PHP 5.5 is the minimum
318  * supported version it will be changed to the PHP PASSWORD_DEFAULT constant.
319  */
320  public static function getCryptedPassword($plaintext, $salt = '', $encryption = 'bcrypt', $show_encrypt = false)
321  {
322  $app = JFactory::getApplication();
323 
324  if ($app->getClientId() != 2)
325  {
326  $joomlaPluginEnabled = JPluginHelper::isEnabled('user', 'joomla');
327  }
328 
329  // The Joomla user plugin allows you to use weaker passwords if necessary.
330  if (!empty($joomlaPluginEnabled))
331  {
332  JPluginHelper::importPlugin('user', 'joomla');
333  $userPlugin = JPluginHelper::getPlugin('user', 'joomla');
334  $userPluginParams = new JRegistry($userPlugin->params);
335  PlgUserJoomla::setDefaultEncryption($userPluginParams);
336  }
337 
338  // Not all controllers check the length, although they should to avoid DOS attacks.
339  // The maximum password length for bcrypt is 55:
340  if (strlen($plaintext) > 55)
341  {
342  $app->enqueueMessage(JText::_('JLIB_USER_ERROR_PASSWORD_TOO_LONG'), 'error');
343 
344  return false;
345  }
346 
347  // Get the salt to use.
348  if (empty($salt))
349  {
350  $salt = self::getSalt($encryption, $salt, $plaintext);
351  }
352 
353  // Encrypt the password.
354  switch ($encryption)
355  {
356  case 'plain':
357  return $plaintext;
358 
359  case 'sha':
360  $encrypted = base64_encode(mhash(MHASH_SHA1, $plaintext));
361 
362  return ($show_encrypt) ? '{SHA}' . $encrypted : $encrypted;
363 
364  case 'crypt':
365  case 'crypt-des':
366  case 'crypt-md5':
367  case 'crypt-blowfish':
368  return ($show_encrypt ? '{crypt}' : '') . crypt($plaintext, $salt);
369 
370  case 'md5-base64':
371  $encrypted = base64_encode(mhash(MHASH_MD5, $plaintext));
372 
373  return ($show_encrypt) ? '{MD5}' . $encrypted : $encrypted;
374 
375  case 'ssha':
376  $encrypted = base64_encode(mhash(MHASH_SHA1, $plaintext . $salt) . $salt);
377 
378  return ($show_encrypt) ? '{SSHA}' . $encrypted : $encrypted;
379 
380  case 'smd5':
381  $encrypted = base64_encode(mhash(MHASH_MD5, $plaintext . $salt) . $salt);
382 
383  return ($show_encrypt) ? '{SMD5}' . $encrypted : $encrypted;
384 
385  case 'aprmd5':
386  $length = strlen($plaintext);
387  $context = $plaintext . '$apr1$' . $salt;
388  $binary = self::_bin(md5($plaintext . $salt . $plaintext));
389 
390  for ($i = $length; $i > 0; $i -= 16)
391  {
392  $context .= substr($binary, 0, ($i > 16 ? 16 : $i));
393  }
394  for ($i = $length; $i > 0; $i >>= 1)
395  {
396  $context .= ($i & 1) ? chr(0) : $plaintext[0];
397  }
398 
399  $binary = self::_bin(md5($context));
400 
401  for ($i = 0; $i < 1000; $i++)
402  {
403  $new = ($i & 1) ? $plaintext : substr($binary, 0, 16);
404 
405  if ($i % 3)
406  {
407  $new .= $salt;
408  }
409  if ($i % 7)
410  {
411  $new .= $plaintext;
412  }
413  $new .= ($i & 1) ? substr($binary, 0, 16) : $plaintext;
414  $binary = self::_bin(md5($new));
415  }
416 
417  $p = array();
418 
419  for ($i = 0; $i < 5; $i++)
420  {
421  $k = $i + 6;
422  $j = $i + 12;
423 
424  if ($j == 16)
425  {
426  $j = 5;
427  }
428  $p[] = self::_toAPRMD5((ord($binary[$i]) << 16) | (ord($binary[$k]) << 8) | (ord($binary[$j])), 5);
429  }
430 
431  return '$apr1$' . $salt . '$' . implode('', $p) . self::_toAPRMD5(ord($binary[11]), 3);
432 
433  case 'md5-hex':
434  $encrypted = ($salt) ? md5($plaintext . $salt) : md5($plaintext);
435 
436  return ($show_encrypt) ? '{MD5}' . $encrypted : $encrypted;
437 
438  case 'sha256':
439  $encrypted = ($salt) ? hash('sha256', $plaintext . $salt) . ':' . $salt : hash('sha256', $plaintext);
440 
441  return ($show_encrypt) ? '{SHA256}' . $encrypted : '{SHA256}' . $encrypted;
442 
443  // 'bcrypt' is the default case starting in CMS 3.2.
444  case 'bcrypt':
445  default:
446  $useStrongEncryption = JCrypt::hasStrongPasswordSupport();
447 
448  if ($useStrongEncryption === true)
449  {
450  $encrypted = password_hash($plaintext, PASSWORD_BCRYPT);
451 
452  if (!$encrypted)
453  {
454  // Something went wrong fall back to sha256.
455  return static::getCryptedPassword($plaintext, '', 'sha256', false);
456  }
457 
458  return ($show_encrypt) ? '{BCRYPT}' . $encrypted : $encrypted;
459  }
460  else
461  {
462  // BCrypt isn't available but we want strong passwords, fall back to sha256.
463  return static::getCryptedPassword($plaintext, '', 'sha256', false);
464  }
465  }
466  }
467 
468  /**
469  * Returns a salt for the appropriate kind of password encryption.
470  * Optionally takes a seed and a plaintext password, to extract the seed
471  * of an existing password, or for encryption types that use the plaintext
472  * in the generation of the salt.
473  *
474  * @param string $encryption The kind of password encryption to use.
475  * Defaults to md5-hex.
476  * @param string $seed The seed to get the salt from (probably a
477  * previously generated password). Defaults to
478  * generating a new seed.
479  * @param string $plaintext The plaintext password that we're generating
480  * a salt for. Defaults to none.
481  *
482  * @return string The generated or extracted salt.
483  *
484  * @since 11.1
485  * @note Default $encryption will be changed to 'bcrypt' in CMS 3.2 and will at
486  * the type used by the PHP PASSWORD_DEFAULT constant until 5.5 is the minimum
487  * version required. At that point the default will be PASSWORD_DEFAULT.
488  */
489  public static function getSalt($encryption = 'md5-hex', $seed = '', $plaintext = '')
490  {
491  // Encrypt the password.
492  switch ($encryption)
493  {
494  case 'crypt':
495  case 'crypt-des':
496  if ($seed)
497  {
498  return substr(preg_replace('|^{crypt}|i', '', $seed), 0, 2);
499  }
500  else
501  {
502  return substr(md5(mt_rand()), 0, 2);
503  }
504  break;
505 
506  case 'sha256':
507  if ($seed)
508  {
509  return preg_replace('|^{sha256}|i', '', $seed);
510  }
511  else
512  {
513  return static::genRandomPassword(16);
514  }
515  break;
516 
517  case 'crypt-md5':
518  if ($seed)
519  {
520  return substr(preg_replace('|^{crypt}|i', '', $seed), 0, 12);
521  }
522  else
523  {
524  return '$1$' . substr(md5(mt_rand()), 0, 8) . '$';
525  }
526  break;
527 
528  case 'crypt-blowfish':
529  if ($seed)
530  {
531  return substr(preg_replace('|^{crypt}|i', '', $seed), 0, 16);
532  }
533  else
534  {
535  return '$2$' . substr(md5(mt_rand()), 0, 12) . '$';
536  }
537  break;
538 
539  case 'ssha':
540  if ($seed)
541  {
542  return substr(preg_replace('|^{SSHA}|', '', $seed), -20);
543  }
544  else
545  {
546  return mhash_keygen_s2k(MHASH_SHA1, $plaintext, substr(pack('h*', md5(mt_rand())), 0, 8), 4);
547  }
548  break;
549 
550  case 'smd5':
551  if ($seed)
552  {
553  return substr(preg_replace('|^{SMD5}|', '', $seed), -16);
554  }
555  else
556  {
557  return mhash_keygen_s2k(MHASH_MD5, $plaintext, substr(pack('h*', md5(mt_rand())), 0, 8), 4);
558  }
559  break;
560 
561  case 'aprmd5': /* 64 characters that are valid for APRMD5 passwords. */
562  $APRMD5 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
563 
564  if ($seed)
565  {
566  return substr(preg_replace('/^\$apr1\$(.{8}).*/', '\\1', $seed), 0, 8);
567  }
568  else
569  {
570  $salt = '';
571 
572  for ($i = 0; $i < 8; $i++)
573  {
574  $salt .= $APRMD5{rand(0, 63)};
575  }
576 
577  return $salt;
578  }
579  break;
580 
581  // BCrypt is aliased because a BCrypt has may be requested when it is not present, and so it falls back to
582  // the default behavior of generating a salt.
583  case 'bcrypt';
584  default:
585  $salt = '';
586 
587  if ($seed)
588  {
589  $salt = $seed;
590  }
591 
592  return $salt;
593  break;
594  }
595  }
596 
597  /**
598  * Generate a random password
599  *
600  * @param integer $length Length of the password to generate
601  *
602  * @return string Random Password
603  *
604  * @since 11.1
605  */
606  public static function genRandomPassword($length = 16)
607  {
608  $salt = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
609  $base = strlen($salt);
610  $makepass = '';
611 
612  /*
613  * Start with a cryptographic strength random string, then convert it to
614  * a string with the numeric base of the salt.
615  * Shift the base conversion on each character so the character
616  * distribution is even, and randomize the start shift so it's not
617  * predictable.
618  */
619  $random = JCrypt::genRandomBytes($length + 1);
620  $shift = ord($random[0]);
621 
622  for ($i = 1; $i <= $length; ++$i)
623  {
624  $makepass .= $salt[($shift + ord($random[$i])) % $base];
625  $shift += ord($random[$i]);
626  }
627 
628  return $makepass;
629  }
630 
631  /**
632  * Converts to allowed 64 characters for APRMD5 passwords.
633  *
634  * @param string $value The value to convert.
635  * @param integer $count The number of characters to convert.
636  *
637  * @return string $value converted to the 64 MD5 characters.
638  *
639  * @since 11.1
640  */
641  protected static function _toAPRMD5($value, $count)
642  {
643  /* 64 characters that are valid for APRMD5 passwords. */
644  $APRMD5 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
645 
646  $aprmd5 = '';
647  $count = abs($count);
648 
649  while (--$count)
650  {
651  $aprmd5 .= $APRMD5[$value & 0x3f];
652  $value >>= 6;
653  }
654  return $aprmd5;
655  }
656 
657  /**
658  * Converts hexadecimal string to binary data.
659  *
660  * @param string $hex Hex data.
661  *
662  * @return string Binary data.
663  *
664  * @since 11.1
665  */
666  private static function _bin($hex)
667  {
668  $bin = '';
669  $length = strlen($hex);
670 
671  for ($i = 0; $i < $length; $i += 2)
672  {
673  $tmp = sscanf(substr($hex, $i, 2), '%x');
674  $bin .= chr(array_shift($tmp));
675  }
676  return $bin;
677  }
678 
679  /**
680  * Method to remove a cookie record from the database and the browser
681  *
682  * @param string $userId User ID for this user
683  * @param string $cookieName Series id (cookie name decoded)
684  *
685  * @return boolean True on success
686  *
687  * @since 3.2
688  * @see JInput::setCookie for more details
689  */
690  public static function invalidateCookie($userId, $cookieName)
691  {
692  $db = JFactory::getDbo();
693  $query = $db->getQuery(true);
694 
695  // Invalidate cookie in the database
696  $query
697  ->update($db->quoteName('#__user_keys'))
698  ->set($db->quoteName('invalid') . ' = 1')
699  ->where($db->quotename('user_id') . ' = ' . $db->quote($userId));
700 
701  $db->setQuery($query)->execute();
702 
703  // Destroy the cookie in the browser.
704  $app = JFactory::getApplication();
705  $app->input->cookie->set($cookieName, false, time() - 42000, $app->get('cookie_path'), $app->get('cookie_domain'), false, true);
706 
707  return true;
708  }
709 
710  /**
711  * Clear all expired tokens for all users.
712  *
713  * @return mixed Database query result
714  *
715  * @since 3.2
716  */
717  public static function clearExpiredTokens()
718  {
719  $now = time();
720 
721  $db = JFactory::getDbo();
722  $query = $db->getQuery(true)
723  ->delete('#__user_keys')
724  ->where($db->quoteName('time') . ' < ' . $db->quote($now));
725 
726  return $db->setQuery($query)->execute();
727  }
728 
729  /**
730  * Method to get the remember me cookie data
731  *
732  * @return mixed An array of information from an authentication cookie or false if there is no cookie
733  *
734  * @since 3.2
735  */
736  public static function getRememberCookieData()
737  {
738  // Create the cookie name
739  $cookieName = static::getShortHashedUserAgent();
740 
741  // Fetch the cookie value
742  $app = JFactory::getApplication();
743  $cookieValue = $app->input->cookie->get($cookieName);
744 
745  if (!empty($cookieValue))
746  {
747  return explode('.', $cookieValue);
748  }
749  else
750  {
751  return false;
752  }
753  }
754 
755  /**
756  * Method to get a hashed user agent string that does not include browser version.
757  * Used when frequent version changes cause problems.
758  *
759  * @return string A hashed user agent string with version replaced by 'abcd'
760  *
761  * @since 3.2
762  */
763  public static function getShortHashedUserAgent()
764  {
765  $ua = JFactory::getApplication()->client;
766  $uaString = $ua->userAgent;
767  $browserVersion = $ua->browserVersion;
768  $uaShort = str_replace($browserVersion, 'abcd', $uaString);
769 
770  return md5(JUri::base() . $uaShort);
771  }
772 }