1<?php 2/** 3 * Matomo - free/libre analytics platform 4 * 5 * @link https://matomo.org 6 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later 7 * 8 */ 9namespace Piwik\Plugins\UsersManager; 10 11use DeviceDetector\DeviceDetector; 12use Exception; 13use Piwik\Access; 14use Piwik\Access\CapabilitiesProvider; 15use Piwik\Access\RolesProvider; 16use Piwik\Auth\Password; 17use Piwik\Common; 18use Piwik\Config; 19use Piwik\Container\StaticContainer; 20use Piwik\Date; 21use Piwik\IP; 22use Piwik\Mail; 23use Piwik\Metrics\Formatter; 24use Piwik\NoAccessException; 25use Piwik\Option; 26use Piwik\Piwik; 27use Piwik\Plugin; 28use Piwik\Plugins\CoreAdminHome\Emails\UserCreatedEmail; 29use Piwik\Plugins\Login\PasswordVerifier; 30use Piwik\SettingsPiwik; 31use Piwik\Site; 32use Piwik\Tracker\Cache; 33use Piwik\View; 34use Piwik\Plugins\CoreAdminHome\Emails\UserDeletedEmail; 35 36/** 37 * The UsersManager API lets you Manage Users and their permissions to access specific websites. 38 * 39 * You can create users via "addUser", update existing users via "updateUser" and delete users via "deleteUser". 40 * There are many ways to list users based on their login "getUser" and "getUsers", their email "getUserByEmail", 41 * or which users have permission (view or admin) to access the specified websites "getUsersWithSiteAccess". 42 * 43 * Existing Permissions are listed given a login via "getSitesAccessFromUser", or a website ID via "getUsersAccessFromSite", 44 * or you can list all users and websites for a given permission via "getUsersSitesFromAccess". Permissions are set and updated 45 * via the method "setUserAccess". 46 * See also the documentation about <a href='http://matomo.org/docs/manage-users/' rel='noreferrer' target='_blank'>Managing Users</a> in Matomo. 47 */ 48class API extends \Piwik\Plugin\API 49{ 50 const OPTION_NAME_PREFERENCE_SEPARATOR = '_'; 51 52 public static $UPDATE_USER_REQUIRE_PASSWORD_CONFIRMATION = true; 53 public static $SET_SUPERUSER_ACCESS_REQUIRE_PASSWORD_CONFIRMATION = true; 54 55 /** 56 * @var Model 57 */ 58 private $model; 59 60 /** 61 * @var Password 62 */ 63 private $password; 64 65 /** 66 * @var UserAccessFilter 67 */ 68 private $userFilter; 69 70 /** 71 * @var Access 72 */ 73 private $access; 74 75 /** 76 * @var Access\RolesProvider 77 */ 78 private $roleProvider; 79 80 /** 81 * @var Access\CapabilitiesProvider 82 */ 83 private $capabilityProvider; 84 85 /** 86 * @var PasswordVerifier 87 */ 88 private $passwordVerifier; 89 90 private $twoFaPluginActivated; 91 92 const PREFERENCE_DEFAULT_REPORT = 'defaultReport'; 93 const PREFERENCE_DEFAULT_REPORT_DATE = 'defaultReportDate'; 94 95 private static $instance = null; 96 97 public function __construct(Model $model, UserAccessFilter $filter, Password $password, Access $access = null, Access\RolesProvider $roleProvider = null, Access\CapabilitiesProvider $capabilityProvider = null, PasswordVerifier $passwordVerifier = null) 98 { 99 $this->model = $model; 100 $this->userFilter = $filter; 101 $this->password = $password; 102 $this->access = $access ?: StaticContainer::get(Access::class); 103 $this->roleProvider = $roleProvider ?: StaticContainer::get(RolesProvider::class); 104 $this->capabilityProvider = $capabilityProvider ?: StaticContainer::get(CapabilitiesProvider::class); 105 $this->passwordVerifier = $passwordVerifier ?: StaticContainer::get(PasswordVerifier::class); 106 } 107 108 /** 109 * You can create your own Users Plugin to override this class. 110 * Example of how you would overwrite the UsersManager_API with your own class: 111 * Call the following in your plugin __construct() for example: 112 * 113 * StaticContainer::getContainer()->set('UsersManager_API', \Piwik\Plugins\MyCustomUsersManager\API::getInstance()); 114 * 115 * @throws Exception 116 * @return \Piwik\Plugins\UsersManager\API 117 */ 118 public static function getInstance() 119 { 120 try { 121 $instance = StaticContainer::get('UsersManager_API'); 122 if (!($instance instanceof API)) { 123 // Exception is caught below and corrected 124 throw new Exception('UsersManager_API must inherit API'); 125 } 126 self::$instance = $instance; 127 128 } catch (Exception $e) { 129 self::$instance = StaticContainer::get('Piwik\Plugins\UsersManager\API'); 130 StaticContainer::getContainer()->set('UsersManager_API', self::$instance); 131 } 132 133 return self::$instance; 134 } 135 136 /** 137 * Get the list of all available roles. 138 * It does not return the super user role, and neither the "noaccess" role. 139 * @return array[] Returns an array containing information about each role 140 */ 141 public function getAvailableRoles() 142 { 143 Piwik::checkUserHasSomeAdminAccess(); 144 145 $response = array(); 146 147 foreach ($this->roleProvider->getAllRoles() as $role) { 148 $response[] = array( 149 'id' => $role->getId(), 150 'name' => $role->getName(), 151 'description' => $role->getDescription(), 152 'helpUrl' => $role->getHelpUrl(), 153 ); 154 } 155 156 return $response; 157 } 158 159 /** 160 * Get the list of all available capabilities. 161 * @return array[] Returns an array containing information about each capability 162 */ 163 public function getAvailableCapabilities() 164 { 165 Piwik::checkUserHasSomeAdminAccess(); 166 167 $response = array(); 168 169 foreach ($this->capabilityProvider->getAllCapabilities() as $capability) { 170 $response[] = array( 171 'id' => $capability->getId(), 172 'name' => $capability->getName(), 173 'description' => $capability->getDescription(), 174 'helpUrl' => $capability->getHelpUrl(), 175 'includedInRoles' => $capability->getIncludedInRoles(), 176 'category' => $capability->getCategory(), 177 ); 178 } 179 180 return $response; 181 } 182 183 /** 184 * Sets a user preference. Plugins can add custom preference names by declaring them in their plugin config/config.php 185 * like this: 186 * 187 * ```php 188 * return array('usersmanager.user_preference_names' => DI\add(array('preference_name_1', 'preference_name_2'))); 189 * ``` 190 * 191 * @param string $userLogin 192 * @param string $preferenceName 193 * @param string $preferenceValue 194 * @return void 195 */ 196 public function setUserPreference($userLogin, $preferenceName, $preferenceValue) 197 { 198 Piwik::checkUserHasSuperUserAccessOrIsTheUser($userLogin); 199 200 if (!$this->model->userExists($userLogin)) { 201 throw new Exception('User does not exist: ' . $userLogin); 202 } 203 204 if ($userLogin === 'anonymous') { 205 Piwik::checkUserHasSuperUserAccess(); 206 } 207 208 $nameIfSupported = $this->getPreferenceId($userLogin, $preferenceName); 209 Option::set($nameIfSupported, $preferenceValue); 210 } 211 212 /** 213 * Gets a user preference 214 * @param string $preferenceName 215 * @param string|bool $userLogin Optional, defaults to current user log in when set to false. 216 * @return bool|string 217 */ 218 public function getUserPreference($preferenceName, $userLogin = false) 219 { 220 if ($userLogin === false) { 221 // the default value for first parameter is there to have it an optional parameter in the HTTP API 222 // in PHP it won't be optional. Could move parameter to the end of the method but did not want to break 223 // BC 224 $userLogin = Piwik::getCurrentUserLogin(); 225 } 226 Piwik::checkUserHasSuperUserAccessOrIsTheUser($userLogin); 227 228 $optionValue = $this->getPreferenceValue($userLogin, $preferenceName); 229 230 if ($optionValue !== false) { 231 return $optionValue; 232 } 233 234 return $this->getDefaultUserPreference($preferenceName, $userLogin); 235 } 236 237 /** 238 * Sets a user preference in the DB using the preference's default value. 239 * @param string $userLogin 240 * @param string $preferenceName 241 * @ignore 242 */ 243 public function initUserPreferenceWithDefault($userLogin, $preferenceName) 244 { 245 Piwik::checkUserHasSuperUserAccessOrIsTheUser($userLogin); 246 247 $optionValue = $this->getPreferenceValue($userLogin, $preferenceName); 248 249 if ($optionValue === false) { 250 $defaultValue = $this->getDefaultUserPreference($preferenceName, $userLogin); 251 252 if ($defaultValue !== false) { 253 $this->setUserPreference($userLogin, $preferenceName, $defaultValue); 254 } 255 } 256 } 257 258 /** 259 * Returns an array of Preferences 260 * @param $preferenceNames array of preference names 261 * @return array 262 * @ignore 263 */ 264 public function getAllUsersPreferences(array $preferenceNames) 265 { 266 Piwik::checkUserHasSuperUserAccess(); 267 268 $userPreferences = array(); 269 foreach($preferenceNames as $preferenceName) { 270 $optionNameMatchAllUsers = $this->getPreferenceId('%', $preferenceName); 271 $preferences = Option::getLike($optionNameMatchAllUsers); 272 273 foreach($preferences as $optionName => $optionValue) { 274 $lastUnderscore = strrpos($optionName, self::OPTION_NAME_PREFERENCE_SEPARATOR); 275 $userName = substr($optionName, 0, $lastUnderscore); 276 $preference = substr($optionName, $lastUnderscore + 1); 277 $userPreferences[$userName][$preference] = $optionValue; 278 } 279 } 280 return $userPreferences; 281 } 282 283 private function getPreferenceId($login, $preference) 284 { 285 if(false !== strpos($preference, self::OPTION_NAME_PREFERENCE_SEPARATOR)) { 286 throw new Exception("Preference name cannot contain underscores."); 287 } 288 $names = array( 289 self::PREFERENCE_DEFAULT_REPORT, 290 self::PREFERENCE_DEFAULT_REPORT_DATE, 291 'isLDAPUser', // used in loginldap 292 'hideSegmentDefinitionChangeMessage',// used in JS 293 ); 294 $customPreferences = StaticContainer::get('usersmanager.user_preference_names'); 295 296 if (!in_array($preference, $names, true) 297 && !in_array($preference, $customPreferences, true)) { 298 throw new Exception('Not supported preference name: ' . $preference); 299 } 300 return $login . self::OPTION_NAME_PREFERENCE_SEPARATOR . $preference; 301 } 302 303 private function getPreferenceValue($userLogin, $preferenceName) 304 { 305 return Option::get($this->getPreferenceId($userLogin, $preferenceName)); 306 } 307 308 private function getDefaultUserPreference($preferenceName, $login) 309 { 310 switch ($preferenceName) { 311 case self::PREFERENCE_DEFAULT_REPORT: 312 $viewableSiteIds = \Piwik\Plugins\SitesManager\API::getInstance()->getSitesIdWithAtLeastViewAccess($login); 313 if (!empty($viewableSiteIds)) { 314 return reset($viewableSiteIds); 315 } 316 return false; 317 case self::PREFERENCE_DEFAULT_REPORT_DATE: 318 return Config::getInstance()->General['default_day']; 319 default: 320 return false; 321 } 322 } 323 324 /** 325 * Returns all users with their role for $idSite. 326 * 327 * @param int $idSite 328 * @param int|null $limit 329 * @param int|null $offset 330 * @param string|null $filter_search text to search for in the user's login and email (if any) 331 * @param string|null $filter_access only select users with this access to $idSite. can be 'noaccess', 'some', 'view', 'admin', 'superuser' 332 * Filtering by 'superuser' is only allowed for other superusers. 333 * @return array 334 */ 335 public function getUsersPlusRole($idSite, $limit = null, $offset = 0, $filter_search = null, $filter_access = null) 336 { 337 if (!$this->isUserHasAdminAccessTo($idSite)) { 338 // if the user is not an admin to $idSite, they can only see their own user 339 if ($offset > 1) { 340 Common::sendHeader('X-Matomo-Total-Results: 1'); 341 return []; 342 } 343 344 $user = $this->model->getUser($this->access->getLogin()); 345 $user['role'] = $this->access->getRoleForSite($idSite); 346 $user['capabilities'] = $this->access->getCapabilitiesForSite($idSite); 347 $users = [$user]; 348 $totalResults = 1; 349 } else { 350 // if the current user is not the superuser, only select users that have access to a site this user 351 // has admin access to 352 $loginsToLimit = null; 353 if (!Piwik::hasUserSuperUserAccess()) { 354 $adminIdSites = Access::getInstance()->getSitesIdWithAdminAccess(); 355 if (empty($adminIdSites)) { // sanity check 356 throw new \Exception("The current admin user does not have access to any sites."); 357 } 358 359 $loginsToLimit = $this->model->getUsersWithAccessToSites($adminIdSites); 360 } 361 362 if ($loginsToLimit !== null && empty($loginsToLimit)) { 363 // if the current user is not the superuser, and getUsersWithAccessToSites() returned an empty result, 364 // access is managed by another plugin, and the current user cannot manage any user with UsersManager 365 Common::sendHeader('X-Matomo-Total-Results: 0'); 366 return []; 367 368 } else { 369 [$users, $totalResults] = $this->model->getUsersWithRole($idSite, $limit, $offset, $filter_search, $filter_access, $loginsToLimit); 370 371 foreach ($users as &$user) { 372 $user['superuser_access'] = $user['superuser_access'] == 1; 373 if ($user['superuser_access']) { 374 $user['role'] = 'superuser'; 375 $user['capabilities'] = []; 376 } else { 377 [$user['role'], $user['capabilities']] = $this->getRoleAndCapabilitiesFromAccess($user['access']); 378 $user['role'] = empty($user['role']) ? 'noaccess' : reset($user['role']); 379 } 380 381 unset($user['access']); 382 } 383 } 384 } 385 386 $users = $this->enrichUsers($users); 387 $users = $this->enrichUsersWithLastSeen($users); 388 389 foreach ($users as &$user) { 390 unset($user['password']); 391 } 392 393 Common::sendHeader('X-Matomo-Total-Results: ' . $totalResults); 394 return $users; 395 } 396 397 /** 398 * Returns the list of all the users 399 * 400 * @param string $userLogins Comma separated list of users to select. If not specified, will return all users 401 * @return array the list of all the users 402 */ 403 public function getUsers($userLogins = '') 404 { 405 Piwik::checkUserHasSomeAdminAccess(); 406 407 if (!is_string($userLogins)) { 408 throw new \Exception('Parameter userLogins needs to be a string containing a comma separated list of users'); 409 } 410 411 $logins = array(); 412 413 if (!empty($userLogins)) { 414 $logins = explode(',', $userLogins); 415 } 416 417 $users = $this->model->getUsers($logins); 418 $users = $this->userFilter->filterUsers($users); 419 $users = $this->enrichUsers($users); 420 421 return $users; 422 } 423 424 /** 425 * Returns the list of all the users login 426 * 427 * @return array the list of all the users login 428 */ 429 public function getUsersLogin() 430 { 431 Piwik::checkUserHasSomeAdminAccess(); 432 433 $logins = $this->model->getUsersLogin(); 434 $logins = $this->userFilter->filterLogins($logins); 435 436 return $logins; 437 } 438 439 /** 440 * For each user, returns the list of website IDs where the user has the supplied $access level. 441 * If a user doesn't have the given $access to any website IDs, 442 * the user will not be in the returned array. 443 * 444 * @param string Access can have the following values : 'view' or 'admin' 445 * 446 * @return array The returned array has the format 447 * array( 448 * login1 => array ( idsite1,idsite2), 449 * login2 => array(idsite2), 450 * ... 451 * ) 452 */ 453 public function getUsersSitesFromAccess($access) 454 { 455 Piwik::checkUserHasSuperUserAccess(); 456 457 $this->checkAccessType($access); 458 459 $userSites = $this->model->getUsersSitesFromAccess($access); 460 $userSites = $this->userFilter->filterLoginIndexedArray($userSites); 461 462 return $userSites; 463 } 464 465 /** 466 * Throws an exception if one of the given access types does not exists. 467 * 468 * @param string|array $access 469 * @throws Exception 470 */ 471 private function checkAccessType($access) 472 { 473 $access = (array) $access; 474 475 foreach ($access as $entry) { 476 if (!$this->isValidAccessType($entry)) { 477 throw new Exception(Piwik::translate("UsersManager_ExceptionAccessValues", [implode(", ", $this->getAllRolesAndCapabilities()), $entry])); 478 } 479 } 480 } 481 482 /** 483 * returns if the given access type exists 484 * 485 * @param string $access 486 * @return bool 487 */ 488 private function isValidAccessType($access) 489 { 490 return in_array($access, $this->getAllRolesAndCapabilities(), true); 491 } 492 493 private function getAllRolesAndCapabilities() 494 { 495 $roles = $this->roleProvider->getAllRoleIds(); 496 $capabilities = $this->capabilityProvider->getAllCapabilityIds(); 497 return array_merge($roles, $capabilities); 498 } 499 500 /** 501 * For each user, returns their access level for the given $idSite. 502 * If a user doesn't have any access to the $idSite ('noaccess'), 503 * the user will not be in the returned array. 504 * 505 * @param int $idSite website ID 506 * 507 * @return array The returned array has the format 508 * array( 509 * login1 => 'view', 510 * login2 => 'admin', 511 * login3 => 'view', 512 * ... 513 * ) 514 */ 515 public function getUsersAccessFromSite($idSite) 516 { 517 Piwik::checkUserHasAdminAccess($idSite); 518 519 $usersAccess = $this->model->getUsersAccessFromSite($idSite); 520 $usersAccess = $this->userFilter->filterLoginIndexedArray($usersAccess); 521 522 return $usersAccess; 523 } 524 525 public function getUsersWithSiteAccess($idSite, $access) 526 { 527 Piwik::checkUserHasAdminAccess($idSite); 528 $this->checkAccessType($access); 529 530 $logins = $this->model->getUsersLoginWithSiteAccess($idSite, $access); 531 532 if (empty($logins)) { 533 return array(); 534 } 535 536 $logins = $this->userFilter->filterLogins($logins); 537 $logins = implode(',', $logins); 538 539 return $this->getUsers($logins); 540 } 541 542 /** 543 * For each website ID, returns the access level of the given $userLogin. 544 * If the user doesn't have any access to a website ('noaccess'), 545 * this website will not be in the returned array. 546 * If the user doesn't have any access, the returned array will be an empty array. 547 * 548 * @param string $userLogin User that has to be valid 549 * 550 * @return array The returned array has the format 551 * array( 552 * idsite1 => 'view', 553 * idsite2 => 'admin', 554 * idsite3 => 'view', 555 * ... 556 * ) 557 */ 558 public function getSitesAccessFromUser($userLogin) 559 { 560 Piwik::checkUserHasSuperUserAccess(); 561 $this->checkUserExists($userLogin); 562 // Super users have 'admin' access for every site 563 if (Piwik::hasTheUserSuperUserAccess($userLogin)) { 564 $return = array(); 565 $siteManagerModel = new \Piwik\Plugins\SitesManager\Model(); 566 $sites = $siteManagerModel->getAllSites(); 567 foreach ($sites as $site) { 568 $return[] = array( 569 'site' => $site['idsite'], 570 'access' => 'admin' 571 ); 572 } 573 return $return; 574 } 575 return $this->model->getSitesAccessFromUser($userLogin); 576 } 577 578 /** 579 * For each website ID, returns the access level of the given $userLogin (if the user is not a superuser). 580 * If the user doesn't have any access to a website ('noaccess'), 581 * this website will not be in the returned array. 582 * If the user doesn't have any access, the returned array will be an empty array. 583 * 584 * @param string $userLogin User that has to be valid 585 * 586 * @param int|null $limit 587 * @param int|null $offset 588 * @param string|null $filter_search text to search for in site name, URLs, or group. 589 * @param string|null $filter_access access level to select for, can be 'some', 'view' or 'admin' (by default 'some') 590 * @return array The returned array has the format 591 * array( 592 * ['idsite' => 1, 'site_name' => 'the site', 'access' => 'admin'], 593 * ['idsite' => 2, 'site_name' => 'the other site', 'access' => 'view'], 594 * ... 595 * ) 596 * @throws Exception 597 */ 598 public function getSitesAccessForUser($userLogin, $limit = null, $offset = 0, $filter_search = null, $filter_access = null) 599 { 600 Piwik::checkUserHasSomeAdminAccess(); 601 $this->checkUserExists($userLogin); 602 603 if (Piwik::hasTheUserSuperUserAccess($userLogin)) { 604 throw new \Exception("This method should not be used with superusers."); 605 } 606 607 $idSites = null; 608 if (!Piwik::hasUserSuperUserAccess()) { 609 $idSites = $this->access->getSitesIdWithAdminAccess(); 610 if (empty($idSites)) { // sanity check 611 throw new \Exception("The current admin user does not have access to any sites."); 612 } 613 } 614 615 [$sites, $totalResults] = $this->model->getSitesAccessFromUserWithFilters($userLogin, $limit, $offset, $filter_search, $filter_access, $idSites); 616 foreach ($sites as &$siteAccess) { 617 [$siteAccess['role'], $siteAccess['capabilities']] = $this->getRoleAndCapabilitiesFromAccess($siteAccess['access']); 618 $siteAccess['role'] = empty($siteAccess['role']) ? 'noaccess' : reset($siteAccess['role']); 619 unset($siteAccess['access']); 620 } 621 622 $hasAccessToAny = $this->model->getSiteAccessCount($userLogin) > 0; 623 624 Common::sendHeader('X-Matomo-Total-Results: ' . $totalResults); 625 if ($hasAccessToAny) { 626 Common::sendHeader('X-Matomo-Has-Some: 1'); 627 } 628 return $sites; 629 } 630 631 /** 632 * Returns the user information (login, password hash, email, date_registered, etc.) 633 * 634 * @param string $userLogin the user login 635 * 636 * @return array the user information 637 */ 638 public function getUser($userLogin) 639 { 640 Piwik::checkUserHasSuperUserAccessOrIsTheUser($userLogin); 641 $this->checkUserExists($userLogin); 642 643 $user = $this->model->getUser($userLogin); 644 645 $user = $this->userFilter->filterUser($user); 646 $user = $this->enrichUser($user); 647 648 return $user; 649 } 650 651 /** 652 * Returns the user information (login, password hash, email, date_registered, etc.) 653 * 654 * @param string $userEmail the user email 655 * 656 * @return array the user information 657 */ 658 public function getUserByEmail($userEmail) 659 { 660 Piwik::checkUserHasSuperUserAccess(); 661 $this->checkUserEmailExists($userEmail); 662 663 $user = $this->model->getUserByEmail($userEmail); 664 665 $user = $this->userFilter->filterUser($user); 666 $user = $this->enrichUser($user); 667 668 return $user; 669 } 670 671 private function checkLogin($userLogin) 672 { 673 if ($this->userExists($userLogin)) { 674 throw new Exception(Piwik::translate('UsersManager_ExceptionLoginExists', $userLogin)); 675 } 676 677 if ($this->userEmailExists($userLogin)) { 678 throw new Exception(Piwik::translate('UsersManager_ExceptionLoginExistsAsEmail', $userLogin)); 679 } 680 681 Piwik::checkValidLoginString($userLogin); 682 } 683 684 private function checkEmail($email, $userLogin = null) 685 { 686 if ($this->userEmailExists($email)) { 687 throw new Exception(Piwik::translate('UsersManager_ExceptionEmailExists', $email)); 688 } 689 690 if ($userLogin && mb_strtolower($userLogin) !== mb_strtolower($email) && $this->userExists($email)) { 691 throw new Exception(Piwik::translate('UsersManager_ExceptionEmailExistsAsLogin', $email)); 692 } 693 694 if (!$userLogin && $this->userExists($email)) { 695 throw new Exception(Piwik::translate('UsersManager_ExceptionEmailExistsAsLogin', $email)); 696 } 697 698 if (!Piwik::isValidEmailString($email)) { 699 throw new Exception(Piwik::translate('UsersManager_ExceptionInvalidEmail')); 700 } 701 } 702 703 /** 704 * Add a user in the database. 705 * A user is defined by 706 * - a login that has to be unique and valid 707 * - a password that has to be valid 708 * - an email that has to be in a correct format 709 * 710 * @see userExists() 711 * @see isValidLoginString() 712 * @see isValidPasswordString() 713 * @see isValidEmailString() 714 * 715 * @exception in case of an invalid parameter 716 */ 717 public function addUser($userLogin, $password, $email, $_isPasswordHashed = false, $initialIdSite = null) 718 { 719 Piwik::checkUserHasSomeAdminAccess(); 720 UsersManager::dieIfUsersAdminIsDisabled(); 721 722 if (!Piwik::hasUserSuperUserAccess()) { 723 if (empty($initialIdSite)) { 724 throw new \Exception(Piwik::translate("UsersManager_AddUserNoInitialAccessError")); 725 } 726 727 Piwik::checkUserHasAdminAccess($initialIdSite); 728 } 729 730 $this->checkLogin($userLogin); 731 $this->checkEmail($email); 732 733 $password = Common::unsanitizeInputValue($password); 734 735 if (!$_isPasswordHashed) { 736 UsersManager::checkPassword($password); 737 738 $passwordTransformed = UsersManager::getPasswordHash($password); 739 } else { 740 $passwordTransformed = $password; 741 } 742 743 $passwordTransformed = $this->password->hash($passwordTransformed); 744 745 $this->model->addUser($userLogin, $passwordTransformed, $email, Date::now()->getDatetime()); 746 747 $container = StaticContainer::getContainer(); 748 $mail = $container->make(UserCreatedEmail::class, array( 749 'login' => Piwik::getCurrentUserLogin(), 750 'emailAddress' => Piwik::getCurrentUserEmail(), 751 'userLogin' => $userLogin 752 )); 753 $mail->safeSend(); 754 755 // we reload the access list which doesn't yet take in consideration this new user 756 Access::getInstance()->reloadAccess(); 757 Cache::deleteTrackerCache(); 758 759 /** 760 * Triggered after a new user is created. 761 * 762 * @param string $userLogin The new user's login handle. 763 */ 764 Piwik::postEvent('UsersManager.addUser.end', array($userLogin, $email, $password)); 765 766 if ($initialIdSite) { 767 $this->setUserAccess($userLogin, 'view', $initialIdSite); 768 } 769 } 770 771 /** 772 * Enable or disable Super user access to the given user login. Note: When granting Super User access all previous 773 * permissions of the user will be removed as the user gains access to everything. 774 * 775 * @param string $userLogin the user login. 776 * @param bool|int $hasSuperUserAccess true or '1' to grant Super User access, false or '0' to remove Super User 777 * access. 778 * @param string $passwordConfirmation the current user's password. For security purposes, this value should be 779 * sent as a POST parameter. 780 * @throws \Exception 781 */ 782 public function setSuperUserAccess($userLogin, $hasSuperUserAccess, $passwordConfirmation = null) 783 { 784 Piwik::checkUserHasSuperUserAccess(); 785 $this->checkUserIsNotAnonymous($userLogin); 786 UsersManager::dieIfUsersAdminIsDisabled(); 787 788 $requirePasswordConfirmation = self::$SET_SUPERUSER_ACCESS_REQUIRE_PASSWORD_CONFIRMATION; 789 self::$SET_SUPERUSER_ACCESS_REQUIRE_PASSWORD_CONFIRMATION = true; 790 791 $isCliMode = Common::isPhpCliMode() && !(defined('PIWIK_TEST_MODE') && PIWIK_TEST_MODE); 792 if (!$isCliMode 793 && $requirePasswordConfirmation 794 ) { 795 $this->confirmCurrentUserPassword($passwordConfirmation); 796 } 797 $this->checkUserExists($userLogin); 798 799 if (!$hasSuperUserAccess && $this->isUserTheOnlyUserHavingSuperUserAccess($userLogin)) { 800 $message = Piwik::translate("UsersManager_ExceptionRemoveSuperUserAccessOnlySuperUser", $userLogin) 801 . " " 802 . Piwik::translate("UsersManager_ExceptionYouMustGrantSuperUserAccessFirst"); 803 throw new Exception($message); 804 } 805 806 $this->model->deleteUserAccess($userLogin); 807 $this->model->setSuperUserAccess($userLogin, $hasSuperUserAccess); 808 809 Cache::deleteTrackerCache(); 810 } 811 812 /** 813 * Detect whether the current user has super user access or not. 814 * 815 * @return bool 816 */ 817 public function hasSuperUserAccess() 818 { 819 return Piwik::hasUserSuperUserAccess(); 820 } 821 822 /** 823 * Returns a list of all Super Users containing there userLogin and email address. 824 * 825 * @return array 826 */ 827 public function getUsersHavingSuperUserAccess() 828 { 829 Piwik::checkUserIsNotAnonymous(); 830 831 $users = $this->model->getUsersHavingSuperUserAccess(); 832 $users = $this->enrichUsers($users); 833 834 // we do not filter these users by access and return them all since we need to print this information in the 835 // UI and they are allowed to see this. 836 837 return $users; 838 } 839 840 private function enrichUsersWithLastSeen($users) 841 { 842 $formatter = new Formatter(); 843 844 $lastSeenTimes = LastSeenTimeLogger::getLastSeenTimesForAllUsers(); 845 foreach ($users as &$user) { 846 $login = $user['login']; 847 if (isset($lastSeenTimes[$login])) { 848 $user['last_seen'] = $formatter->getPrettyTimeFromSeconds(time() - $lastSeenTimes[$login]); 849 } 850 } 851 return $users; 852 } 853 854 private function enrichUsers($users) 855 { 856 if (!empty($users)) { 857 foreach ($users as $index => $user) { 858 $users[$index] = $this->enrichUser($user); 859 } 860 } 861 return $users; 862 } 863 864 private function isTwoFactorAuthPluginEnabled() 865 { 866 if (!isset($this->twoFaPluginActivated)) { 867 $this->twoFaPluginActivated = Plugin\Manager::getInstance()->isPluginActivated('TwoFactorAuth'); 868 } 869 return $this->twoFaPluginActivated; 870 } 871 872 private function enrichUser($user) 873 { 874 if (empty($user)) { 875 return $user; 876 } 877 878 unset($user['token_auth']); 879 unset($user['password']); 880 unset($user['ts_password_modified']); 881 882 if ($lastSeen = LastSeenTimeLogger::getLastSeenTimeForUser($user['login'])) { 883 $user['last_seen'] = Date::getDatetimeFromTimestamp($lastSeen); 884 } 885 886 if (Piwik::hasUserSuperUserAccess()) { 887 $user['uses_2fa'] = !empty($user['twofactor_secret']) && $this->isTwoFactorAuthPluginEnabled(); 888 unset($user['twofactor_secret']); 889 return $user; 890 } 891 892 $newUser = array('login' => $user['login']); 893 894 if ($user['login'] === Piwik::getCurrentUserLogin() || !empty($user['superuser_access'])) { 895 $newUser['email'] = $user['email']; 896 } 897 898 if (isset($user['role'])) { 899 $newUser['role'] = $user['role'] == 'superuser' ? 'admin' : $user['role']; 900 } 901 if (isset($user['capabilities'])) { 902 $newUser['capabilities'] = $user['capabilities']; 903 } 904 905 if (isset($user['superuser_access'])) { 906 $newUser['superuser_access'] = $user['superuser_access']; 907 } 908 909 if (isset($user['last_seen'])) { 910 $newUser['last_seen'] = $user['last_seen']; 911 } 912 913 return $newUser; 914 } 915 916 /** 917 * Updates a user in the database. 918 * Only login and password are required (case when we update the password). 919 * 920 * If password or email changes, it is required to also specify the password of the current user needs to be specified 921 * to confirm this change. 922 * 923 * @see addUser() for all the parameters 924 */ 925 public function updateUser($userLogin, $password = false, $email = false, 926 $_isPasswordHashed = false, $passwordConfirmation = false) 927 { 928 $requirePasswordConfirmation = self::$UPDATE_USER_REQUIRE_PASSWORD_CONFIRMATION; 929 self::$UPDATE_USER_REQUIRE_PASSWORD_CONFIRMATION = true; 930 931 $isEmailNotificationOnInConfig = Config::getInstance()->General['enable_update_users_email']; 932 933 Piwik::checkUserHasSuperUserAccessOrIsTheUser($userLogin); 934 UsersManager::dieIfUsersAdminIsDisabled(); 935 $this->checkUserIsNotAnonymous($userLogin); 936 $this->checkUserExists($userLogin); 937 938 $userInfo = $this->model->getUser($userLogin); 939 $changeShouldRequirePasswordConfirmation = false; 940 941 $passwordHasBeenUpdated = false; 942 943 if (empty($password)) { 944 $password = false; 945 } else { 946 $changeShouldRequirePasswordConfirmation = true; 947 $password = Common::unsanitizeInputValue($password); 948 949 if (!$_isPasswordHashed) { 950 UsersManager::checkPassword($password); 951 $password = UsersManager::getPasswordHash($password); 952 } 953 954 $passwordInfo = $this->password->info($password); 955 956 if (!isset($passwordInfo['algo']) || 0 >= $passwordInfo['algo']) { 957 // password may have already been fully hashed 958 $password = $this->password->hash($password); 959 } 960 961 $passwordHasBeenUpdated = true; 962 } 963 964 if (empty($email)) { 965 $email = $userInfo['email']; 966 } 967 968 $hasEmailChanged = mb_strtolower($email) !== mb_strtolower($userInfo['email']); 969 970 if ($hasEmailChanged) { 971 $this->checkEmail($email, $userLogin); 972 $changeShouldRequirePasswordConfirmation = true; 973 } 974 975 if ($changeShouldRequirePasswordConfirmation && $requirePasswordConfirmation) { 976 $this->confirmCurrentUserPassword($passwordConfirmation); 977 } 978 979 $this->model->updateUser($userLogin, $password, $email); 980 981 Cache::deleteTrackerCache(); 982 983 if ($hasEmailChanged && $isEmailNotificationOnInConfig) { 984 $this->sendEmailChangedEmail($userInfo, $email); 985 } 986 987 if ($passwordHasBeenUpdated && $requirePasswordConfirmation && $isEmailNotificationOnInConfig) { 988 $this->sendPasswordChangedEmail($userInfo); 989 } 990 991 /** 992 * Triggered after an existing user has been updated. 993 * Event notify about password change. 994 * 995 * @param string $userLogin The user's login handle. 996 * @param boolean $passwordHasBeenUpdated Flag containing information about password change. 997 */ 998 Piwik::postEvent('UsersManager.updateUser.end', array($userLogin, $passwordHasBeenUpdated, $email, $password)); 999 } 1000 1001 /** 1002 * Delete one or more users and all its access, given its login. 1003 * 1004 * @param string $userLogin the user login(s). 1005 * 1006 * @throws Exception if the user doesn't exist or if deleting the users would leave no superusers. 1007 * 1008 * @return bool true on success 1009 */ 1010 public function deleteUser($userLogin) 1011 { 1012 Piwik::checkUserHasSuperUserAccess(); 1013 UsersManager::dieIfUsersAdminIsDisabled(); 1014 $this->checkUserIsNotAnonymous($userLogin); 1015 1016 $this->checkUserExist($userLogin); 1017 1018 if ($this->isUserTheOnlyUserHavingSuperUserAccess($userLogin)) { 1019 $message = Piwik::translate("UsersManager_ExceptionDeleteOnlyUserWithSuperUserAccess", $userLogin) 1020 . " " 1021 . Piwik::translate("UsersManager_ExceptionYouMustGrantSuperUserAccessFirst"); 1022 throw new Exception($message); 1023 } 1024 1025 $this->model->deleteUserOnly($userLogin); 1026 $this->model->deleteUserOptions($userLogin); 1027 $this->model->deleteUserAccess($userLogin); 1028 1029 $container = StaticContainer::getContainer(); 1030 $email = $container->make(UserDeletedEmail::class, array( 1031 'login' => Piwik::getCurrentUserLogin(), 1032 'emailAddress' => Piwik::getCurrentUserEmail(), 1033 'userLogin' => $userLogin 1034 )); 1035 $email->safeSend(); 1036 1037 Cache::deleteTrackerCache(); 1038 } 1039 1040 /** 1041 * Returns true if the given userLogin is known in the database 1042 * 1043 * @param string $userLogin 1044 * @return bool true if the user is known 1045 */ 1046 public function userExists($userLogin) 1047 { 1048 if ($userLogin == 'anonymous') { 1049 return true; 1050 } 1051 1052 Piwik::checkUserIsNotAnonymous(); 1053 Piwik::checkUserHasSomeViewAccess(); 1054 1055 if ($userLogin == Piwik::getCurrentUserLogin()) { 1056 return true; 1057 } 1058 1059 return $this->model->userExists($userLogin); 1060 } 1061 1062 /** 1063 * Returns true if user with given email (userEmail) is known in the database, or the Super User 1064 * 1065 * @param string $userEmail 1066 * @return bool true if the user is known 1067 */ 1068 public function userEmailExists($userEmail) 1069 { 1070 Piwik::checkUserIsNotAnonymous(); 1071 Piwik::checkUserHasSomeViewAccess(); 1072 1073 return $this->model->userEmailExists($userEmail); 1074 } 1075 1076 /** 1077 * Returns the first login name of an existing user that has the given email address. If no user can be found for 1078 * this user an error will be returned. 1079 * 1080 * @param string $userEmail 1081 * @return bool true if the user is known 1082 */ 1083 public function getUserLoginFromUserEmail($userEmail) 1084 { 1085 Piwik::checkUserIsNotAnonymous(); 1086 Piwik::checkUserHasSomeAdminAccess(); 1087 1088 $this->checkUserEmailExists($userEmail); 1089 1090 $user = $this->model->getUserByEmail($userEmail); 1091 1092 // any user with some admin access is allowed to find any user by email, no need to filter by access here 1093 1094 return $user['login']; 1095 } 1096 1097 /** 1098 * Set an access level to a given user for a list of websites ID. 1099 * 1100 * If access = 'noaccess' the current access (if any) will be deleted. 1101 * If access = 'view' or 'admin' the current access level is deleted and updated with the new value. 1102 * 1103 * @param string $userLogin The user login 1104 * @param string|array $access Access to grant. Must have one of the following value : noaccess, view, write, admin. 1105 * May also be an array to sent additional capabilities 1106 * @param int|array $idSites The array of idSites on which to apply the access level for the user. 1107 * 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. 1108 * @throws Exception if the user doesn't exist 1109 * @throws Exception if the access parameter doesn't have a correct value 1110 * @throws Exception if any of the given website ID doesn't exist 1111 */ 1112 public function setUserAccess($userLogin, $access, $idSites) 1113 { 1114 UsersManager::dieIfUsersAdminIsDisabled(); 1115 1116 if ($access != 'noaccess') { 1117 $this->checkAccessType($access); 1118 } 1119 1120 $idSites = $this->getIdSitesCheckAdminAccess($idSites); 1121 1122 if ($userLogin === 'anonymous' && 1123 (is_array($access) || !in_array($access, array('view', 'noaccess'), true)) 1124 ) { 1125 throw new Exception(Piwik::translate("UsersManager_ExceptionAnonymousAccessNotPossible", array('noaccess', 'view'))); 1126 } 1127 1128 $roles = array(); 1129 $capabilities = array(); 1130 1131 if (is_array($access)) { 1132 // we require one role, and optionally multiple capabilities 1133 [$roles, $capabilities] = $this->getRoleAndCapabilitiesFromAccess($access); 1134 1135 if (count($roles) < 1) { 1136 $ids = implode(', ', $this->roleProvider->getAllRoleIds()); 1137 throw new Exception(Piwik::translate('UsersManager_ExceptionNoRoleSet', $ids)); 1138 } 1139 1140 if (count($roles) > 1) { 1141 $ids = implode(', ', $this->roleProvider->getAllRoleIds()); 1142 throw new Exception(Piwik::translate('UsersManager_ExceptionMultipleRoleSet', $ids)); 1143 } 1144 1145 } else { 1146 // as only one access is set, we require it to be a role or "noaccess"... 1147 if ($access !== 'noaccess') { 1148 $this->roleProvider->checkValidRole($access); 1149 $roles[] = $access; 1150 } 1151 } 1152 1153 $this->checkUserExist($userLogin); 1154 $this->checkUsersHasNotSuperUserAccess($userLogin); 1155 1156 $this->model->deleteUserAccess($userLogin, $idSites); 1157 1158 if ($access === 'noaccess') { 1159 // if the access is noaccess then we don't save it as this is the default value 1160 // when no access are specified 1161 Piwik::postEvent('UsersManager.removeSiteAccess', array($userLogin, $idSites)); 1162 } else { 1163 $role = array_shift($roles); 1164 $this->model->addUserAccess($userLogin, $role, $idSites); 1165 } 1166 1167 if (!empty($capabilities)) { 1168 $this->addCapabilities($userLogin, $capabilities, $idSites); 1169 } 1170 1171 // we reload the access list which doesn't yet take in consideration this new user access 1172 $this->reloadPermissions(); 1173 } 1174 1175 /** 1176 * Adds the given capabilities to the given user for the given sites. 1177 * The capability will be added only when the user also has access to a site, for example View, Write, or Admin. 1178 * Note: You can neither add any capability to a super user, nor to the anonymous user. 1179 * Note: If the user has assigned a role which already grants the given capability, the capability will not be added in 1180 * the backend. 1181 * 1182 * @param string $userLogin The user login 1183 * @param string|string[] $capabilities To fetch a list of available capabilities call "UsersManager.getAvailableCapabilities". 1184 * @param int|int[] $idSites 1185 * @throws Exception 1186 */ 1187 public function addCapabilities($userLogin, $capabilities, $idSites) 1188 { 1189 $idSites = $this->getIdSitesCheckAdminAccess($idSites); 1190 1191 if ($userLogin == 'anonymous') { 1192 throw new Exception(Piwik::translate("UsersManager_ExceptionAnonymousNoCapabilities")); 1193 } 1194 1195 $this->checkUserExists($userLogin); 1196 $this->checkUsersHasNotSuperUserAccess([$userLogin]); 1197 1198 if (!is_array($capabilities)){ 1199 $capabilities = array($capabilities); 1200 } 1201 1202 foreach ($capabilities as $entry) { 1203 $this->capabilityProvider->checkValidCapability($entry); 1204 } 1205 1206 [$sitesIdWithRole, $sitesIdWithCapability] = $this->getRolesAndCapabilitiesForLogin($userLogin); 1207 1208 foreach ($capabilities as $entry) { 1209 $cap = $this->capabilityProvider->getCapability($entry); 1210 1211 foreach ($idSites as $idSite) { 1212 $hasRole = array_key_exists($idSite, $sitesIdWithRole); 1213 $hasCapabilityAlready = array_key_exists($idSite, $sitesIdWithCapability) && in_array($entry, $sitesIdWithCapability[$idSite], true); 1214 1215 // so far we are adding the capability only to people that also have a role... 1216 // to be defined how to handle this... eg we are not throwing an exception currently 1217 // as it might be used as part of bulk action etc. 1218 if ($hasRole && !$hasCapabilityAlready) { 1219 $theRole = $sitesIdWithRole[$idSite]; 1220 if ($cap->hasRoleCapability($theRole)) { 1221 // todo this behaviour needs to be defined... 1222 // when the role already supports this capability we do not add it again 1223 continue; 1224 } 1225 1226 $this->model->addUserAccess($userLogin, $entry, array($idSite)); 1227 } 1228 } 1229 1230 } 1231 1232 // we reload the access list which doesn't yet take in consideration this new user access 1233 $this->reloadPermissions(); 1234 } 1235 1236 private function getRolesAndCapabilitiesForLogin($userLogin) 1237 { 1238 $sites = $this->model->getSitesAccessFromUser($userLogin); 1239 $roleIds = $this->roleProvider->getAllRoleIds(); 1240 1241 $sitesIdWithRole = array(); 1242 $sitesIdWithCapability = array(); 1243 foreach ($sites as $site) { 1244 if (in_array($site['access'], $roleIds, true)) { 1245 $sitesIdWithRole[(int) $site['site']] = $site['access']; 1246 } else { 1247 if (!isset($sitesIdWithCapability[(int) $site['site']])) { 1248 $sitesIdWithCapability[(int) $site['site']] = array(); 1249 } 1250 $sitesIdWithCapability[(int) $site['site']][] = $site['access']; 1251 } 1252 } 1253 return [$sitesIdWithRole, $sitesIdWithCapability]; 1254 } 1255 1256 /** 1257 * Removes the given capabilities from the given user for the given sites. 1258 * The capability will be only removed if it is actually granted as a separate capability. If the user has a role 1259 * that includes a specific capability, for example "Admin", then the capability will not be removed because the 1260 * assigned role will always include this capability. 1261 * 1262 * @param string $userLogin The user login 1263 * @param string|string[] $capabilities To fetch a list of available capabilities call "UsersManager.getAvailableCapabilities". 1264 * @param int|int[] $idSites 1265 * @throws Exception 1266 */ 1267 public function removeCapabilities($userLogin, $capabilities, $idSites) 1268 { 1269 $idSites = $this->getIdSitesCheckAdminAccess($idSites); 1270 1271 $this->checkUserExists($userLogin); 1272 1273 if (!is_array($capabilities)){ 1274 $capabilities = array($capabilities); 1275 } 1276 1277 foreach ($capabilities as $capability) { 1278 $this->capabilityProvider->checkValidCapability($capability); 1279 } 1280 1281 foreach ($capabilities as $capability) { 1282 $this->model->removeUserAccess($userLogin, $capability, $idSites); 1283 } 1284 1285 // we reload the access list which doesn't yet take in consideration this removed capability 1286 $this->reloadPermissions(); 1287 } 1288 1289 private function reloadPermissions() 1290 { 1291 Access::getInstance()->reloadAccess(); 1292 Cache::deleteTrackerCache(); 1293 } 1294 1295 private function getIdSitesCheckAdminAccess($idSites) 1296 { 1297 // in case idSites is all we grant access to all the websites on which the current connected user has an 'admin' access 1298 if ($idSites === 'all') { 1299 $idSites = \Piwik\Plugins\SitesManager\API::getInstance()->getSitesIdWithAdminAccess(); 1300 } // in case the idSites is an integer we build an array 1301 else { 1302 $idSites = Site::getIdSitesFromIdSitesString($idSites); 1303 } 1304 1305 if (empty($idSites)) { 1306 throw new Exception('Specify at least one website ID in &idSites='); 1307 } 1308 1309 // it is possible to set user access on websites only for the websites admin 1310 // basically an admin can give the view or the admin access to any user for the websites they manage 1311 Piwik::checkUserHasAdminAccess($idSites); 1312 1313 if (!is_array($idSites)) { 1314 $idSites = array($idSites); 1315 } 1316 1317 return $idSites; 1318 } 1319 1320 /** 1321 * Throws an exception is the user login doesn't exist 1322 * 1323 * @param string $userLogin user login 1324 * @throws Exception if the user doesn't exist 1325 */ 1326 private function checkUserExists($userLogin) 1327 { 1328 if (!$this->userExists($userLogin)) { 1329 throw new Exception(Piwik::translate("UsersManager_ExceptionUserDoesNotExist", $userLogin)); 1330 } 1331 } 1332 1333 /** 1334 * Throws an exception is the user email cannot be found 1335 * 1336 * @param string $userEmail user email 1337 * @throws Exception if the user doesn't exist 1338 */ 1339 private function checkUserEmailExists($userEmail) 1340 { 1341 if (!$this->userEmailExists($userEmail)) { 1342 throw new Exception(Piwik::translate("UsersManager_ExceptionUserDoesNotExist", $userEmail)); 1343 } 1344 } 1345 1346 private function checkUserIsNotAnonymous($userLogin) 1347 { 1348 if ($userLogin == 'anonymous') { 1349 throw new Exception(Piwik::translate("UsersManager_ExceptionEditAnonymous")); 1350 } 1351 } 1352 1353 private function checkUsersHasNotSuperUserAccess($userLogins) 1354 { 1355 $userLogins = (array) $userLogins; 1356 $superusers = $this->getUsersHavingSuperUserAccess(); 1357 $superusers = array_column($superusers, null, 'login'); 1358 1359 foreach ($userLogins as $userLogin) { 1360 if (isset($superusers[$userLogin])) { 1361 throw new Exception(Piwik::translate("UsersManager_ExceptionUserHasSuperUserAccess", $userLogin)); 1362 } 1363 } 1364 } 1365 1366 /** 1367 * @param string|string[] $userLogin 1368 * @return bool 1369 */ 1370 private function isUserTheOnlyUserHavingSuperUserAccess($userLogin) 1371 { 1372 if (!is_array($userLogin)) { 1373 $userLogin = [$userLogin]; 1374 } 1375 1376 $superusers = $this->getUsersHavingSuperUserAccess(); 1377 $superusersByLogin = array_column($superusers, null, 'login'); 1378 1379 foreach ($userLogin as $login) { 1380 unset($superusersByLogin[$login]); 1381 } 1382 1383 return empty($superusersByLogin); 1384 } 1385 1386 /** 1387 * Generates an app specific API token every time you call this method. You should ideally store this token securely 1388 * in your app and not generate a new token every time. 1389 * 1390 * If the username/password combination is incorrect an invalid token will be returned. 1391 * 1392 * @param string $userLogin Login or Email address 1393 * @param string $passwordConfirmation the current user's password. For security purposes, this value should be 1394 * sent as a POST parameter. 1395 * @param string $description The description for this app specific password, for example your app name. Max 100 characters are allowed 1396 * @param string $expireDate Optionally a date when the token should expire 1397 * @param string $expireHours Optionally number of hours for how long the token should be valid before it expires. 1398 * If expireDate is set and expireHours, then expireDate will be used. 1399 * If expireDate is set and expireHours, then expireDate will be used. 1400 * @return string 1401 */ 1402 public function createAppSpecificTokenAuth($userLogin, $passwordConfirmation, $description, $expireDate = null, $expireHours = 0) 1403 { 1404 $user = $this->model->getUser($userLogin); 1405 if (empty($user) && Piwik::isValidEmailString($userLogin)) { 1406 $user = $this->model->getUserByEmail($userLogin); 1407 if (!empty($user['login'])) { 1408 $userLogin = $user['login']; 1409 } 1410 } 1411 1412 if (empty($user) || !$this->passwordVerifier->isPasswordCorrect($userLogin, $passwordConfirmation)) { 1413 if (empty($user)) { 1414 /** 1415 * @ignore 1416 * @internal 1417 */ 1418 Piwik::postEvent('Login.authenticate.failed', array($userLogin)); 1419 } 1420 1421 throw new \Exception(Piwik::translate('UsersManager_CurrentPasswordNotCorrect')); 1422 } 1423 1424 if (empty($expireDate) && !empty($expireHours) && is_numeric($expireHours)) { 1425 $expireDate = Date::now()->addHour($expireHours)->getDatetime(); 1426 } elseif (!empty($expireDate)) { 1427 $expireDate = Date::factory($expireDate)->getDatetime(); 1428 } 1429 1430 $generatedToken = $this->model->generateRandomTokenAuth(); 1431 $this->model->addTokenAuth($userLogin, $generatedToken, $description, Date::now()->getDatetime(), $expireDate); 1432 1433 return $generatedToken; 1434 } 1435 1436 public function newsletterSignup() 1437 { 1438 Piwik::checkUserIsNotAnonymous(); 1439 1440 $userLogin = Piwik::getCurrentUserLogin(); 1441 $email = Piwik::getCurrentUserEmail(); 1442 1443 $success = NewsletterSignup::signupForNewsletter($userLogin, $email, true); 1444 $result = $success ? array('success' => true) : array('error' => true); 1445 return $result; 1446 } 1447 1448 private function isUserHasAdminAccessTo($idSite) 1449 { 1450 try { 1451 Piwik::checkUserHasAdminAccess([$idSite]); 1452 return true; 1453 } catch (NoAccessException $ex) { 1454 return false; 1455 } 1456 } 1457 1458 private function checkUserExist($userLogin) 1459 { 1460 $userExists = $this->model->userExists($userLogin); 1461 if (!$userExists) { 1462 throw new Exception(Piwik::translate("UsersManager_ExceptionUserDoesNotExist", $userLogin)); 1463 } 1464 } 1465 1466 private function getRoleAndCapabilitiesFromAccess($access) 1467 { 1468 $roles = []; 1469 $capabilities = []; 1470 1471 foreach ($access as $entry) { 1472 if (empty($entry)) { 1473 continue; 1474 } 1475 1476 if ($this->roleProvider->isValidRole($entry)) { 1477 $roles[] = $entry; 1478 } else { 1479 if ($this->isValidAccessType($entry)) { 1480 $capabilities[] = $entry; 1481 } 1482 } 1483 } 1484 return [$roles, $capabilities]; 1485 } 1486 1487 private function confirmCurrentUserPassword($passwordConfirmation) 1488 { 1489 if (empty($passwordConfirmation)) { 1490 throw new Exception(Piwik::translate('UsersManager_ConfirmWithPassword')); 1491 } 1492 1493 $passwordConfirmation = Common::unsanitizeInputValue($passwordConfirmation); 1494 1495 $loginCurrentUser = Piwik::getCurrentUserLogin(); 1496 if (!$this->passwordVerifier->isPasswordCorrect($loginCurrentUser, $passwordConfirmation)) { 1497 throw new Exception(Piwik::translate('UsersManager_CurrentPasswordNotCorrect')); 1498 } 1499 } 1500 1501 private function sendEmailChangedEmail($user, $newEmail) 1502 { 1503 // send the mail to both the old email and the new email 1504 foreach ([$newEmail, $user['email']] as $emailTo) { 1505 $this->sendUserInfoChangedEmail('email', $user, $newEmail, $emailTo, 'UsersManager_EmailChangeNotificationSubject'); 1506 } 1507 } 1508 1509 private function sendUserInfoChangedEmail($type, $user, $newValue, $emailTo, $subject) 1510 { 1511 $deviceDescription = $this->getDeviceDescription(); 1512 1513 $view = new View('@UsersManager/_userInfoChangedEmail.twig'); 1514 $view->type = $type; 1515 $view->accountName = Common::sanitizeInputValue($user['login']); 1516 $view->newEmail = Common::sanitizeInputValue($newValue); 1517 $view->ipAddress = IP::getIpFromHeader(); 1518 $view->deviceDescription = $deviceDescription; 1519 1520 $mail = new Mail(); 1521 1522 $mail->addTo($emailTo, $user['login']); 1523 $mail->setSubject(Piwik::translate($subject)); 1524 $mail->setDefaultFromPiwik(); 1525 $mail->setWrappedHtmlBody($view); 1526 1527 $replytoEmailName = Config::getInstance()->General['login_password_recovery_replyto_email_name']; 1528 $replytoEmailAddress = Config::getInstance()->General['login_password_recovery_replyto_email_address']; 1529 $mail->addReplyTo($replytoEmailAddress, $replytoEmailName); 1530 1531 $mail->send(); 1532 } 1533 1534 private function sendPasswordChangedEmail($user) 1535 { 1536 $this->sendUserInfoChangedEmail('password', $user, null, $user['email'], 'UsersManager_PasswordChangeNotificationSubject'); 1537 } 1538 1539 private function getDeviceDescription() 1540 { 1541 $userAgent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : ''; 1542 1543 $uaParser = new DeviceDetector($userAgent); 1544 $uaParser->parse(); 1545 1546 $deviceName = ucfirst($uaParser->getDeviceName()); 1547 if (!empty($deviceName)) { 1548 $description = $deviceName; 1549 } else { 1550 $description = Piwik::translate('General_Unknown'); 1551 } 1552 1553 $deviceBrand = $uaParser->getBrandName(); 1554 $deviceModel = $uaParser->getModel(); 1555 if (!empty($deviceBrand) 1556 || !empty($deviceModel) 1557 ) { 1558 $parts = array_filter([$deviceBrand, $deviceModel]); 1559 $description .= ' (' . implode(' ', $parts) . ')'; 1560 } 1561 1562 return $description; 1563 } 1564} 1565