PageRenderTime 31ms CodeModel.GetById 0ms RepoModel.GetById 0ms app.codeStats 0ms

/plugins/UsersManager/API.php

https://github.com/quarkness/piwik
PHP | 718 lines | 415 code | 72 blank | 231 comment | 37 complexity | b975802a49f191616ff10017e423e70b MD5 | raw file
  1. <?php
  2. /**
  3. * Piwik - Open source web analytics
  4. *
  5. * @link http://piwik.org
  6. * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  7. * @version $Id$
  8. *
  9. * @category Piwik_Plugins
  10. * @package Piwik_UsersManager
  11. */
  12. /**
  13. * The UsersManager API lets you Manage Users and their permissions to access specific websites.
  14. *
  15. * You can create users via "addUser", update existing users via "updateUser" and delete users via "deleteUser".
  16. * There are many ways to list users based on their login "getUser" and "getUsers", their email "getUserByEmail",
  17. * or which users have permission (view or admin) to access the specified websites "getUsersWithSiteAccess".
  18. *
  19. * Existing Permissions are listed given a login via "getSitesAccessFromUser", or a website ID via "getUsersAccessFromSite",
  20. * or you can list all users and websites for a given permission via "getUsersSitesFromAccess". Permissions are set and updated
  21. * via the method "setUserAccess".
  22. * See also the documentation about <a href='http://piwik.org/docs/manage-users/' target='_blank'>Managing Users</a> in Piwik.
  23. * @package Piwik_UsersManager
  24. */
  25. class Piwik_UsersManager_API
  26. {
  27. static private $instance = null;
  28. /**
  29. * You can create your own Users Plugin to override this class.
  30. * Example of how you would overwrite the UsersManager_API with your own class:
  31. * Call the following in your plugin __construct() for example:
  32. *
  33. * Zend_Registry::set('UsersManager_API',Piwik_MyCustomUsersManager_API::getInstance());
  34. *
  35. * @return Piwik_UsersManager_API
  36. */
  37. static public function getInstance()
  38. {
  39. try {
  40. $instance = Zend_Registry::get('UsersManager_API');
  41. if( !($instance instanceof Piwik_UsersManager_API) ) {
  42. // Exception is caught below and corrected
  43. throw new Exception('UsersManager_API must inherit Piwik_UsersManager_API');
  44. }
  45. self::$instance = $instance;
  46. }
  47. catch (Exception $e) {
  48. self::$instance = new self;
  49. Zend_Registry::set('UsersManager_API', self::$instance);
  50. }
  51. return self::$instance;
  52. }
  53. const PREFERENCE_DEFAULT_REPORT = 'defaultReport';
  54. const PREFERENCE_DEFAULT_REPORT_DATE = 'defaultReportDate';
  55. /**
  56. * Sets a user preference
  57. * @param string $userLogin
  58. * @param string $preferenceName
  59. * @param string $preferenceValue
  60. * @return void
  61. */
  62. public function setUserPreference($userLogin, $preferenceName, $preferenceValue)
  63. {
  64. Piwik::checkUserIsSuperUserOrTheUser($userLogin);
  65. Piwik_SetOption($this->getPreferenceId($userLogin, $preferenceName), $preferenceValue);
  66. }
  67. /**
  68. * Gets a user preference
  69. * @param string $userLogin
  70. * @param string $preferenceName
  71. * @param string $preferenceValue
  72. * @return void
  73. */
  74. public function getUserPreference($userLogin, $preferenceName)
  75. {
  76. Piwik::checkUserIsSuperUserOrTheUser($userLogin);
  77. return Piwik_GetOption($this->getPreferenceId($userLogin, $preferenceName));
  78. }
  79. private function getPreferenceId($login, $preference)
  80. {
  81. return $login . '_' . $preference;
  82. }
  83. /**
  84. * Returns the list of all the users
  85. *
  86. * @param string Comma separated list of users to select. If not specified, will return all users
  87. * @return array the list of all the users
  88. */
  89. public function getUsers( $userLogins = '' )
  90. {
  91. Piwik::checkUserHasSomeAdminAccess();
  92. $where = '';
  93. $bind = array();
  94. if(!empty($userLogins))
  95. {
  96. $userLogins = explode(',', $userLogins);
  97. $where = 'WHERE login IN ('. Piwik_Common::getSqlStringFieldsArray($userLogins).')';
  98. $bind = $userLogins;
  99. }
  100. $db = Zend_Registry::get('db');
  101. $users = $db->fetchAll("SELECT *
  102. FROM ".Piwik_Common::prefixTable("user")."
  103. $where
  104. ORDER BY login ASC", $bind);
  105. // Non Super user can only access login & alias
  106. if(!Piwik::isUserIsSuperUser())
  107. {
  108. foreach($users as &$user)
  109. {
  110. $user = array('login' => $user['login'], 'alias' => $user['alias'] );
  111. }
  112. }
  113. return $users;
  114. }
  115. /**
  116. * Returns the list of all the users login
  117. *
  118. * @return array the list of all the users login
  119. */
  120. public function getUsersLogin()
  121. {
  122. Piwik::checkUserHasSomeAdminAccess();
  123. $db = Zend_Registry::get('db');
  124. $users = $db->fetchAll("SELECT login
  125. FROM ".Piwik_Common::prefixTable("user")."
  126. ORDER BY login ASC");
  127. $return = array();
  128. foreach($users as $login)
  129. {
  130. $return[] = $login['login'];
  131. }
  132. return $return;
  133. }
  134. /**
  135. * For each user, returns the list of website IDs where the user has the supplied $access level.
  136. * If a user doesn't have the given $access to any website IDs,
  137. * the user will not be in the returned array.
  138. *
  139. * @param string Access can have the following values : 'view' or 'admin'
  140. *
  141. * @return array The returned array has the format
  142. * array(
  143. * login1 => array ( idsite1,idsite2),
  144. * login2 => array(idsite2),
  145. * ...
  146. * )
  147. *
  148. */
  149. public function getUsersSitesFromAccess( $access )
  150. {
  151. Piwik::checkUserIsSuperUser();
  152. $this->checkAccessType($access);
  153. $db = Zend_Registry::get('db');
  154. $users = $db->fetchAll("SELECT login,idsite
  155. FROM ".Piwik_Common::prefixTable("access")
  156. ." WHERE access = ?
  157. ORDER BY login, idsite", $access);
  158. $return = array();
  159. foreach($users as $user)
  160. {
  161. $return[$user['login']][] = $user['idsite'];
  162. }
  163. return $return;
  164. }
  165. /**
  166. * For each user, returns his access level for the given $idSite.
  167. * If a user doesn't have any access to the $idSite ('noaccess'),
  168. * the user will not be in the returned array.
  169. *
  170. * @param string website ID
  171. *
  172. * @return array The returned array has the format
  173. * array(
  174. * login1 => 'view',
  175. * login2 => 'admin',
  176. * login3 => 'view',
  177. * ...
  178. * )
  179. */
  180. public function getUsersAccessFromSite( $idSite )
  181. {
  182. Piwik::checkUserHasAdminAccess( $idSite );
  183. $db = Zend_Registry::get('db');
  184. $users = $db->fetchAll("SELECT login,access
  185. FROM ".Piwik_Common::prefixTable("access")
  186. ." WHERE idsite = ?", $idSite);
  187. $return = array();
  188. foreach($users as $user)
  189. {
  190. $return[$user['login']] = $user['access'];
  191. }
  192. return $return;
  193. }
  194. public function getUsersWithSiteAccess( $idSite, $access )
  195. {
  196. Piwik::checkUserHasAdminAccess( $idSite );
  197. $this->checkAccessType( $access );
  198. $db = Zend_Registry::get('db');
  199. $users = $db->fetchAll("SELECT login
  200. FROM ".Piwik_Common::prefixTable("access")
  201. ." WHERE idsite = ? AND access = ?", array($idSite, $access));
  202. $logins = array();
  203. foreach($users as $user)
  204. {
  205. $logins[] = $user['login'];
  206. }
  207. if(empty($logins))
  208. {
  209. return array();
  210. }
  211. $logins = implode(',', $logins);
  212. return $this->getUsers($logins);
  213. }
  214. /**
  215. * For each website ID, returns the access level of the given $userLogin.
  216. * If the user doesn't have any access to a website ('noaccess'),
  217. * this website will not be in the returned array.
  218. * If the user doesn't have any access, the returned array will be an empty array.
  219. *
  220. * @param string User that has to be valid
  221. *
  222. * @return array The returned array has the format
  223. * array(
  224. * idsite1 => 'view',
  225. * idsite2 => 'admin',
  226. * idsite3 => 'view',
  227. * ...
  228. * )
  229. */
  230. public function getSitesAccessFromUser( $userLogin )
  231. {
  232. Piwik::checkUserIsSuperUser();
  233. $this->checkUserExists($userLogin);
  234. $this->checkUserIsNotSuperUser($userLogin);
  235. $db = Zend_Registry::get('db');
  236. $users = $db->fetchAll("SELECT idsite,access
  237. FROM ".Piwik_Common::prefixTable("access")
  238. ." WHERE login = ?", $userLogin);
  239. $return = array();
  240. foreach($users as $user)
  241. {
  242. $return[] = array(
  243. 'site' => $user['idsite'],
  244. 'access' => $user['access'],
  245. );
  246. }
  247. return $return;
  248. }
  249. /**
  250. * Returns the user information (login, password md5, alias, email, date_registered, etc.)
  251. *
  252. * @param string the user login
  253. *
  254. * @return array the user information
  255. */
  256. public function getUser( $userLogin )
  257. {
  258. Piwik::checkUserIsSuperUserOrTheUser($userLogin);
  259. $this->checkUserExists($userLogin);
  260. $this->checkUserIsNotSuperUser($userLogin);
  261. $db = Zend_Registry::get('db');
  262. $user = $db->fetchRow("SELECT *
  263. FROM ".Piwik_Common::prefixTable("user")
  264. ." WHERE login = ?", $userLogin);
  265. return $user;
  266. }
  267. /**
  268. * Returns the user information (login, password md5, alias, email, date_registered, etc.)
  269. *
  270. * @param string the user email
  271. *
  272. * @return array the user information
  273. */
  274. public function getUserByEmail( $userEmail )
  275. {
  276. Piwik::checkUserIsSuperUser();
  277. $this->checkUserEmailExists($userEmail);
  278. $db = Zend_Registry::get('db');
  279. $user = $db->fetchRow("SELECT *
  280. FROM ".Piwik_Common::prefixTable("user")
  281. ." WHERE email = ?", $userEmail);
  282. return $user;
  283. }
  284. private function checkLogin($userLogin)
  285. {
  286. if($this->userExists($userLogin))
  287. {
  288. throw new Exception(Piwik_TranslateException('UsersManager_ExceptionLoginExists', $userLogin));
  289. }
  290. Piwik::checkValidLoginString($userLogin);
  291. }
  292. private function checkPassword($password)
  293. {
  294. if(!$this->isValidPasswordString($password))
  295. {
  296. throw new Exception(Piwik_TranslateException('UsersManager_ExceptionInvalidPassword', array(self::PASSWORD_MIN_LENGTH, self::PASSWORD_MAX_LENGTH)));
  297. }
  298. }
  299. const PASSWORD_MIN_LENGTH = 6;
  300. const PASSWORD_MAX_LENGTH = 26;
  301. private function checkEmail($email)
  302. {
  303. if($this->userEmailExists($email))
  304. {
  305. throw new Exception(Piwik_TranslateException('UsersManager_ExceptionEmailExists', $email));
  306. }
  307. if(!Piwik::isValidEmailString($email))
  308. {
  309. throw new Exception(Piwik_TranslateException('UsersManager_ExceptionInvalidEmail'));
  310. }
  311. }
  312. private function getCleanAlias($alias,$userLogin)
  313. {
  314. if(empty($alias))
  315. {
  316. $alias = $userLogin;
  317. }
  318. return $alias;
  319. }
  320. private function getCleanPassword($password)
  321. {
  322. // if change here, should also edit the installation process
  323. // to change how the root pwd is saved in the config file
  324. return md5($password);
  325. }
  326. /**
  327. * Add a user in the database.
  328. * A user is defined by
  329. * - a login that has to be unique and valid
  330. * - a password that has to be valid
  331. * - an alias
  332. * - an email that has to be in a correct format
  333. *
  334. * @see userExists()
  335. * @see isValidLoginString()
  336. * @see isValidPasswordString()
  337. * @see isValidEmailString()
  338. *
  339. * @exception in case of an invalid parameter
  340. */
  341. public function addUser( $userLogin, $password, $email, $alias = false )
  342. {
  343. Piwik::checkUserIsSuperUser();
  344. $this->checkLogin($userLogin);
  345. $this->checkUserIsNotSuperUser($userLogin);
  346. $this->checkEmail($email);
  347. $password = Piwik_Common::unsanitizeInputValue($password);
  348. $this->checkPassword($password);
  349. $alias = $this->getCleanAlias($alias,$userLogin);
  350. $passwordTransformed = $this->getCleanPassword($password);
  351. $token_auth = $this->getTokenAuth($userLogin, $passwordTransformed);
  352. $db = Zend_Registry::get('db');
  353. $db->insert( Piwik_Common::prefixTable("user"), array(
  354. 'login' => $userLogin,
  355. 'password' => $passwordTransformed,
  356. 'alias' => $alias,
  357. 'email' => $email,
  358. 'token_auth' => $token_auth,
  359. 'date_registered' => Piwik_Date::now()->getDatetime()
  360. )
  361. );
  362. // we reload the access list which doesn't yet take in consideration this new user
  363. Zend_Registry::get('access')->reloadAccess();
  364. Piwik_Common::deleteTrackerCache();
  365. }
  366. /**
  367. * Updates a user in the database.
  368. * Only login and password are required (case when we update the password).
  369. * When the password changes, the key token for this user will change, which could break
  370. * its API calls.
  371. *
  372. * @see addUser() for all the parameters
  373. */
  374. public function updateUser( $userLogin, $password = false, $email = false, $alias = false )
  375. {
  376. Piwik::checkUserIsSuperUserOrTheUser($userLogin);
  377. $this->checkUserIsNotAnonymous( $userLogin );
  378. $this->checkUserIsNotSuperUser($userLogin);
  379. $userInfo = $this->getUser($userLogin);
  380. if(empty($password))
  381. {
  382. $password = $userInfo['password'];
  383. }
  384. else
  385. {
  386. $password = Piwik_Common::unsanitizeInputValue($password);
  387. $this->checkPassword($password);
  388. $password = $this->getCleanPassword($password);
  389. }
  390. if(empty($alias))
  391. {
  392. $alias = $userInfo['alias'];
  393. }
  394. if(empty($email))
  395. {
  396. $email = $userInfo['email'];
  397. }
  398. if($email != $userInfo['email'])
  399. {
  400. $this->checkEmail($email);
  401. }
  402. $alias = $this->getCleanAlias($alias,$userLogin);
  403. $token_auth = $this->getTokenAuth($userLogin,$password);
  404. $db = Zend_Registry::get('db');
  405. $db->update( Piwik_Common::prefixTable("user"),
  406. array(
  407. 'password' => $password,
  408. 'alias' => $alias,
  409. 'email' => $email,
  410. 'token_auth' => $token_auth,
  411. ),
  412. "login = '$userLogin'"
  413. );
  414. Piwik_Common::deleteTrackerCache();
  415. }
  416. /**
  417. * Delete a user and all its access, given its login.
  418. *
  419. * @param string the user login.
  420. *
  421. * @exception if the user doesn't exist
  422. *
  423. * @return bool true on success
  424. */
  425. public function deleteUser( $userLogin )
  426. {
  427. Piwik::checkUserIsSuperUser();
  428. $this->checkUserIsNotAnonymous( $userLogin );
  429. $this->checkUserIsNotSuperUser($userLogin);
  430. if(!$this->userExists($userLogin))
  431. {
  432. throw new Exception(Piwik_TranslateException("UsersManager_ExceptionDeleteDoesNotExist", $userLogin));
  433. }
  434. $this->deleteUserOnly( $userLogin );
  435. $this->deleteUserAccess( $userLogin );
  436. Piwik_Common::deleteTrackerCache();
  437. }
  438. /**
  439. * Returns true if the given userLogin is known in the database
  440. *
  441. * @return bool true if the user is known
  442. */
  443. public function userExists( $userLogin )
  444. {
  445. $count = Piwik_FetchOne("SELECT count(*)
  446. FROM ".Piwik_Common::prefixTable("user"). "
  447. WHERE login = ?", $userLogin);
  448. return $count != 0;
  449. }
  450. /**
  451. * Returns true if user with given email (userEmail) is known in the database
  452. *
  453. * @return bool true if the user is known
  454. */
  455. public function userEmailExists( $userEmail )
  456. {
  457. Piwik::checkUserHasSomeAdminAccess();
  458. $count = Piwik_FetchOne("SELECT count(*)
  459. FROM ".Piwik_Common::prefixTable("user"). "
  460. WHERE email = ?", $userEmail);
  461. return $count != 0;
  462. }
  463. /**
  464. * Set an access level to a given user for a list of websites ID.
  465. *
  466. * If access = 'noaccess' the current access (if any) will be deleted.
  467. * If access = 'view' or 'admin' the current access level is deleted and updated with the new value.
  468. *
  469. * @param string Access to grant. Must have one of the following value : noaccess, view, admin
  470. * @param string The user login
  471. * @param int|array The array of idSites on which to apply the access level for the user.
  472. * If the value is "all" then we apply the access level to all the websites ID for which the current authentificated user has an 'admin' access.
  473. *
  474. * @exception if the user doesn't exist
  475. * @exception if the access parameter doesn't have a correct value
  476. * @exception if any of the given website ID doesn't exist
  477. *
  478. * @return bool true on success
  479. */
  480. public function setUserAccess( $userLogin, $access, $idSites)
  481. {
  482. $this->checkAccessType( $access );
  483. $this->checkUserExists( $userLogin);
  484. $this->checkUserIsNotSuperUser($userLogin);
  485. if($userLogin == 'anonymous'
  486. && $access == 'admin')
  487. {
  488. throw new Exception(Piwik_TranslateException("UsersManager_ExceptionAdminAnonymous"));
  489. }
  490. // in case idSites is null we grant access to all the websites on which the current connected user
  491. // has an 'admin' access
  492. if($idSites === 'all')
  493. {
  494. $idSites = Piwik_SitesManager_API::getInstance()->getSitesIdWithAdminAccess();
  495. }
  496. // in case the idSites is an integer we build an array
  497. elseif(!is_array($idSites))
  498. {
  499. $idSites = Piwik_Site::getIdSitesFromIdSitesString($idSites);
  500. }
  501. // it is possible to set user access on websites only for the websites admin
  502. // basically an admin can give the view or the admin access to any user for the websites he manages
  503. Piwik::checkUserHasAdminAccess( $idSites );
  504. $this->deleteUserAccess( $userLogin, $idSites);
  505. // delete UserAccess
  506. $db = Zend_Registry::get('db');
  507. // if the access is noaccess then we don't save it as this is the default value
  508. // when no access are specified
  509. if($access != 'noaccess')
  510. {
  511. foreach($idSites as $idsite)
  512. {
  513. $db->insert( Piwik_Common::prefixTable("access"),
  514. array( "idsite" => $idsite,
  515. "login" => $userLogin,
  516. "access" => $access)
  517. );
  518. }
  519. }
  520. // we reload the access list which doesn't yet take in consideration this new user access
  521. Zend_Registry::get('access')->reloadAccess();
  522. Piwik_Common::deleteTrackerCache();
  523. }
  524. /**
  525. * Throws an exception is the user login doesn't exist
  526. *
  527. * @param string user login
  528. * @exception if the user doesn't exist
  529. */
  530. private function checkUserExists( $userLogin )
  531. {
  532. if(!$this->userExists($userLogin))
  533. {
  534. throw new Exception(Piwik_TranslateException("UsersManager_ExceptionUserDoesNotExist", $userLogin));
  535. }
  536. }
  537. /**
  538. * Throws an exception is the user email cannot be found
  539. *
  540. * @param string user email
  541. * @exception if the user doesn't exist
  542. */
  543. private function checkUserEmailExists( $userEmail )
  544. {
  545. if(!$this->userEmailExists($userEmail))
  546. {
  547. throw new Exception(Piwik_TranslateException("UsersManager_ExceptionUserDoesNotExist", $userEmail));
  548. }
  549. }
  550. private function checkUserIsNotAnonymous( $userLogin )
  551. {
  552. if($userLogin == 'anonymous')
  553. {
  554. throw new Exception(Piwik_TranslateException("UsersManager_ExceptionEditAnonymous"));
  555. }
  556. }
  557. private function checkUserIsNotSuperUser( $userLogin )
  558. {
  559. if($userLogin == Zend_Registry::get('config')->superuser->login)
  560. {
  561. throw new Exception(Piwik_TranslateException("UsersManager_ExceptionSuperUser"));
  562. }
  563. }
  564. private function checkAccessType($access)
  565. {
  566. $accessList = Piwik_Access::getListAccess();
  567. // do not allow to set the superUser access
  568. unset($accessList[array_search("superuser", $accessList)]);
  569. if(!in_array($access,$accessList))
  570. {
  571. throw new Exception(Piwik_TranslateException("UsersManager_ExceptionAccessValues", implode(", ", $accessList)));
  572. }
  573. }
  574. /**
  575. * Delete a user given its login.
  576. * The user's access are not deleted.
  577. *
  578. * @param string the user login.
  579. *
  580. */
  581. private function deleteUserOnly( $userLogin )
  582. {
  583. $db = Zend_Registry::get('db');
  584. $db->query("DELETE FROM ".Piwik_Common::prefixTable("user")." WHERE login = ?", $userLogin);
  585. Piwik_PostEvent('UsersManager.deleteUser', $userLogin);
  586. }
  587. /**
  588. * Delete the user access for the given websites.
  589. * The array of idsite must be either null OR the values must have been checked before for their validity!
  590. *
  591. * @param string the user login
  592. * @param array array of idsites on which to delete the access. If null then delete all the access for this user.
  593. *
  594. * @return bool true on success
  595. */
  596. private function deleteUserAccess( $userLogin, $idSites = null )
  597. {
  598. $db = Zend_Registry::get('db');
  599. if(is_null($idSites))
  600. {
  601. $db->query( "DELETE FROM ".Piwik_Common::prefixTable("access").
  602. " WHERE login = ?",
  603. array( $userLogin) );
  604. }
  605. else
  606. {
  607. foreach($idSites as $idsite)
  608. {
  609. $db->query( "DELETE FROM ".Piwik_Common::prefixTable("access").
  610. " WHERE idsite = ? AND login = ?",
  611. array($idsite, $userLogin)
  612. );
  613. }
  614. }
  615. }
  616. /**
  617. * Generates a unique MD5 for the given login & password
  618. *
  619. * @param string Login
  620. * @param string MD5ied string of the password
  621. */
  622. public function getTokenAuth($userLogin, $md5Password)
  623. {
  624. if(strlen($md5Password) != 32)
  625. {
  626. throw new Exception(Piwik_TranslateException('UsersManager_ExceptionPasswordMD5HashExpected'));
  627. }
  628. return md5($userLogin . $md5Password );
  629. }
  630. /**
  631. * Returns true if the password is complex enough (at least 6 characters and max 26 characters)
  632. *
  633. * @param string email
  634. * @return bool
  635. */
  636. private function isValidPasswordString( $input )
  637. {
  638. if(!Piwik::isChecksEnabled()
  639. && !empty($input))
  640. {
  641. return true;
  642. }
  643. $l = strlen($input);
  644. return $l >= self::PASSWORD_MIN_LENGTH && $l <= self::PASSWORD_MAX_LENGTH;
  645. }
  646. }