2014-05-28 01:09:08 +04:00
< ? php
/**
2016-07-21 18:07:57 +03:00
* @ copyright Copyright ( c ) 2016 , ownCloud , Inc .
*
2015-03-26 13:44:34 +03:00
* @ author Bernhard Posselt < dev @ bernhard - posselt . com >
2017-11-06 17:56:42 +03:00
* @ author Bjoern Schiessle < bjoern @ schiessle . org >
2016-05-26 20:56:05 +03:00
* @ author Björn Schießle < bjoern @ schiessle . org >
2016-07-21 18:07:57 +03:00
* @ author Joas Schilling < coding @ schilljs . com >
2016-05-26 20:56:05 +03:00
* @ author Julius Haertl < jus @ bitgrid . net >
* @ author Lukas Reschke < lukas @ statuscode . ch >
2015-03-26 13:44:34 +03:00
* @ author Morris Jobke < hey @ morrisjobke . de >
2016-07-21 18:07:57 +03:00
* @ author Roeland Jago Douma < roeland @ famdouma . nl >
2015-03-26 13:44:34 +03:00
* @ author Thomas Müller < thomas . mueller @ tmit . eu >
* @ author Victor Dubiniuk < dubiniuk @ owncloud . com >
*
* @ license AGPL - 3.0
*
* This code is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License , version 3 ,
* as published by the Free Software Foundation .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Affero General Public License for more details .
*
* You should have received a copy of the GNU Affero General Public License , version 3 ,
* along with this program . If not , see < http :// www . gnu . org / licenses />
*
2014-05-28 01:09:08 +04:00
*/
2015-02-26 13:37:37 +03:00
2016-01-20 12:42:19 +03:00
namespace OC\Core\Controller ;
2014-05-28 01:09:08 +04:00
2019-11-22 22:52:10 +03:00
use function array_filter ;
use function count ;
2019-01-28 18:12:06 +03:00
use OC\Authentication\TwoFactorAuth\Manager ;
2019-07-26 16:21:41 +03:00
use OC\Core\Exception\ResetPasswordException ;
2018-05-20 13:51:50 +03:00
use OC\HintException ;
2019-11-22 22:52:10 +03:00
use OCP\AppFramework\Controller ;
2017-04-14 14:42:40 +03:00
use OCP\AppFramework\Http\JSONResponse ;
2019-11-22 22:52:10 +03:00
use OCP\AppFramework\Http\TemplateResponse ;
2015-08-22 21:42:45 +03:00
use OCP\AppFramework\Utility\ITimeFactory ;
2017-04-07 23:42:43 +03:00
use OCP\Defaults ;
2018-08-10 17:12:34 +03:00
use OCP\Encryption\IEncryptionModule ;
2016-09-29 17:38:29 +03:00
use OCP\Encryption\IManager ;
2019-11-22 22:52:10 +03:00
use OCP\IConfig ;
2019-07-25 18:04:33 +03:00
use OCP\IInitialStateService ;
2019-11-22 22:52:10 +03:00
use OCP\IL10N ;
2019-01-14 23:05:52 +03:00
use OCP\ILogger ;
2019-11-22 22:52:10 +03:00
use OCP\IRequest ;
use OCP\IURLGenerator ;
2017-03-28 21:39:36 +03:00
use OCP\IUser ;
2014-10-20 21:05:48 +04:00
use OCP\IUserManager ;
2015-02-12 18:03:51 +03:00
use OCP\Mail\IMailer ;
2016-08-28 15:22:29 +03:00
use OCP\Security\ICrypto ;
2014-10-20 21:05:48 +04:00
use OCP\Security\ISecureRandom ;
2019-07-09 12:58:14 +03:00
use function reset ;
2014-05-28 01:09:08 +04:00
2014-10-20 21:05:48 +04:00
/**
* Class LostController
*
2014-10-24 15:41:44 +04:00
* Successfully changing a password will emit the post_passwordReset hook .
*
2016-01-20 12:42:19 +03:00
* @ package OC\Core\Controller
2014-10-20 21:05:48 +04:00
*/
2014-05-28 01:09:08 +04:00
class LostController extends Controller {
2014-10-20 21:05:48 +04:00
/** @var IURLGenerator */
2014-05-28 01:09:08 +04:00
protected $urlGenerator ;
2014-10-20 21:05:48 +04:00
/** @var IUserManager */
2014-06-06 18:36:10 +04:00
protected $userManager ;
2017-04-07 23:42:43 +03:00
/** @var Defaults */
2014-05-28 21:13:07 +04:00
protected $defaults ;
2014-10-20 21:05:48 +04:00
/** @var IL10N */
2014-05-28 21:13:07 +04:00
protected $l10n ;
2014-10-20 21:05:48 +04:00
/** @var string */
2014-05-28 21:13:07 +04:00
protected $from ;
2016-09-29 17:38:29 +03:00
/** @var IManager */
protected $encryptionManager ;
2014-10-20 21:05:48 +04:00
/** @var IConfig */
2014-06-06 18:36:10 +04:00
protected $config ;
2014-10-20 21:05:48 +04:00
/** @var ISecureRandom */
protected $secureRandom ;
2015-02-12 18:03:51 +03:00
/** @var IMailer */
protected $mailer ;
2015-08-22 21:42:45 +03:00
/** @var ITimeFactory */
protected $timeFactory ;
2016-08-28 15:22:29 +03:00
/** @var ICrypto */
protected $crypto ;
2019-01-14 23:05:52 +03:00
/** @var ILogger */
private $logger ;
2019-01-28 18:12:06 +03:00
/** @var Manager */
private $twoFactorManager ;
2019-07-25 18:04:33 +03:00
/** @var IInitialStateService */
private $initialStateService ;
2014-07-24 14:50:39 +04:00
/**
2014-10-20 21:05:48 +04:00
* @ param string $appName
* @ param IRequest $request
* @ param IURLGenerator $urlGenerator
2014-10-24 15:41:44 +04:00
* @ param IUserManager $userManager
2017-04-07 23:42:43 +03:00
* @ param Defaults $defaults
2014-10-20 21:05:48 +04:00
* @ param IL10N $l10n
* @ param IConfig $config
* @ param ISecureRandom $secureRandom
2016-09-29 17:38:29 +03:00
* @ param string $defaultMailAddress
* @ param IManager $encryptionManager
2015-02-12 18:03:51 +03:00
* @ param IMailer $mailer
2015-08-22 21:42:45 +03:00
* @ param ITimeFactory $timeFactory
2016-08-28 15:22:29 +03:00
* @ param ICrypto $crypto
2014-07-24 14:50:39 +04:00
*/
2014-06-06 18:36:10 +04:00
public function __construct ( $appName ,
2014-10-20 21:05:48 +04:00
IRequest $request ,
IURLGenerator $urlGenerator ,
IUserManager $userManager ,
2017-04-07 23:42:43 +03:00
Defaults $defaults ,
2014-10-20 21:05:48 +04:00
IL10N $l10n ,
IConfig $config ,
ISecureRandom $secureRandom ,
2016-09-29 17:38:29 +03:00
$defaultMailAddress ,
IManager $encryptionManager ,
2015-08-22 21:42:45 +03:00
IMailer $mailer ,
2016-08-28 15:22:29 +03:00
ITimeFactory $timeFactory ,
2019-01-14 23:05:52 +03:00
ICrypto $crypto ,
2019-01-28 18:12:06 +03:00
ILogger $logger ,
2019-07-25 18:04:33 +03:00
Manager $twoFactorManager ,
IInitialStateService $initialStateService ) {
2014-05-28 01:09:08 +04:00
parent :: __construct ( $appName , $request );
$this -> urlGenerator = $urlGenerator ;
2014-06-06 18:36:10 +04:00
$this -> userManager = $userManager ;
2014-05-28 21:13:07 +04:00
$this -> defaults = $defaults ;
$this -> l10n = $l10n ;
2014-10-20 21:05:48 +04:00
$this -> secureRandom = $secureRandom ;
2016-09-29 17:38:29 +03:00
$this -> from = $defaultMailAddress ;
$this -> encryptionManager = $encryptionManager ;
2014-06-06 18:36:10 +04:00
$this -> config = $config ;
2015-02-12 18:03:51 +03:00
$this -> mailer = $mailer ;
2015-08-22 21:42:45 +03:00
$this -> timeFactory = $timeFactory ;
2016-08-28 15:22:29 +03:00
$this -> crypto = $crypto ;
2019-01-14 23:05:52 +03:00
$this -> logger = $logger ;
2019-01-28 18:12:06 +03:00
$this -> twoFactorManager = $twoFactorManager ;
2019-07-25 18:04:33 +03:00
$this -> initialStateService = $initialStateService ;
2014-05-28 01:09:08 +04:00
}
/**
2014-06-06 18:36:10 +04:00
* Someone wants to reset their password :
*
2014-05-28 01:09:08 +04:00
* @ PublicPage
* @ NoCSRFRequired
2014-06-06 18:36:10 +04:00
*
2014-05-28 21:13:07 +04:00
* @ param string $token
2014-06-13 18:18:21 +04:00
* @ param string $userId
2014-10-20 21:05:48 +04:00
* @ return TemplateResponse
2014-05-28 01:09:08 +04:00
*/
2014-06-13 18:18:21 +04:00
public function resetform ( $token , $userId ) {
2017-05-11 17:46:43 +03:00
if ( $this -> config -> getSystemValue ( 'lost_password_link' , '' ) !== '' ) {
return new TemplateResponse ( 'core' , 'error' , [
'errors' => [[ 'error' => $this -> l10n -> t ( 'Password reset is disabled' )]]
],
'guest'
);
}
2016-05-19 14:23:12 +03:00
try {
$this -> checkPasswordResetToken ( $token , $userId );
} catch ( \Exception $e ) {
return new TemplateResponse (
'core' , 'error' , [
" errors " => array ( array ( " error " => $e -> getMessage ()))
],
'guest'
);
}
2019-07-25 18:04:33 +03:00
$this -> initialStateService -> provideInitialState ( 'core' , 'resetPasswordUser' , $userId );
$this -> initialStateService -> provideInitialState ( 'core' , 'resetPasswordTarget' ,
$this -> urlGenerator -> linkToRouteAbsolute ( 'core.lost.setPassword' , [ 'userId' => $userId , 'token' => $token ])
);
2016-05-19 14:23:12 +03:00
2014-06-06 18:36:10 +04:00
return new TemplateResponse (
2016-01-20 12:42:19 +03:00
'core' ,
2019-07-25 18:04:33 +03:00
'login' ,
[],
2014-06-06 18:36:10 +04:00
'guest'
);
2014-05-28 01:09:08 +04:00
}
2014-06-06 18:36:10 +04:00
2016-05-19 14:23:12 +03:00
/**
2016-08-29 22:17:16 +03:00
* @ param string $token
2016-05-19 14:23:12 +03:00
* @ param string $userId
* @ throws \Exception
*/
2016-12-08 13:38:23 +03:00
protected function checkPasswordResetToken ( $token , $userId ) {
2016-05-19 14:23:12 +03:00
$user = $this -> userManager -> get ( $userId );
2017-08-18 14:03:40 +03:00
if ( $user === null || ! $user -> isEnabled ()) {
2016-08-28 15:22:29 +03:00
throw new \Exception ( $this -> l10n -> t ( 'Couldn\'t reset password because the token is invalid' ));
}
2019-08-18 20:58:50 +03:00
$encryptedToken = $this -> config -> getUserValue ( $userId , 'core' , 'lostpassword' , null );
if ( $encryptedToken === null ) {
throw new \Exception ( $this -> l10n -> t ( 'Couldn\'t reset password because the token is invalid' ));
}
2016-08-28 15:22:29 +03:00
try {
$mailAddress = ! is_null ( $user -> getEMailAddress ()) ? $user -> getEMailAddress () : '' ;
$decryptedToken = $this -> crypto -> decrypt ( $encryptedToken , $mailAddress . $this -> config -> getSystemValue ( 'secret' ));
} catch ( \Exception $e ) {
throw new \Exception ( $this -> l10n -> t ( 'Couldn\'t reset password because the token is invalid' ));
}
2016-05-19 14:23:12 +03:00
2016-08-28 15:22:29 +03:00
$splittedToken = explode ( ':' , $decryptedToken );
2016-05-19 14:23:12 +03:00
if ( count ( $splittedToken ) !== 2 ) {
throw new \Exception ( $this -> l10n -> t ( 'Couldn\'t reset password because the token is invalid' ));
}
2018-08-31 10:26:09 +03:00
if ( $splittedToken [ 0 ] < ( $this -> timeFactory -> getTime () - 60 * 60 * 24 * 7 ) ||
2016-05-19 14:23:12 +03:00
$user -> getLastLogin () > $splittedToken [ 0 ]) {
throw new \Exception ( $this -> l10n -> t ( 'Couldn\'t reset password because the token is expired' ));
}
2016-08-29 22:17:16 +03:00
if ( ! hash_equals ( $splittedToken [ 1 ], $token )) {
2016-05-19 14:23:12 +03:00
throw new \Exception ( $this -> l10n -> t ( 'Couldn\'t reset password because the token is invalid' ));
}
}
2014-10-20 21:05:48 +04:00
/**
* @ param $message
* @ param array $additional
* @ return array
*/
2014-06-06 18:51:58 +04:00
private function error ( $message , array $additional = array ()) {
2014-06-13 18:18:39 +04:00
return array_merge ( array ( 'status' => 'error' , 'msg' => $message ), $additional );
2014-06-06 18:51:58 +04:00
}
2014-10-20 21:05:48 +04:00
/**
2018-06-19 18:02:20 +03:00
* @ param array $data
2014-10-20 21:05:48 +04:00
* @ return array
*/
2018-06-19 18:02:20 +03:00
private function success ( $data = []) {
return array_merge ( $data , [ 'status' => 'success' ]);
2014-06-06 18:51:58 +04:00
}
2014-05-28 21:13:07 +04:00
/**
* @ PublicPage
2017-04-12 21:32:48 +03:00
* @ BruteForceProtection ( action = passwordResetEmail )
2017-04-22 09:12:54 +03:00
* @ AnonRateThrottle ( limit = 10 , period = 300 )
2014-06-06 18:36:10 +04:00
*
* @ param string $user
2017-04-14 14:42:40 +03:00
* @ return JSONResponse
2014-05-28 21:13:07 +04:00
*/
2014-10-20 21:05:48 +04:00
public function email ( $user ){
2017-05-11 17:46:43 +03:00
if ( $this -> config -> getSystemValue ( 'lost_password_link' , '' ) !== '' ) {
return new JSONResponse ( $this -> error ( $this -> l10n -> t ( 'Password reset is disabled' )));
}
2017-12-22 17:22:49 +03:00
\OCP\Util :: emitHook (
'\OCA\Files_Sharing\API\Server2Server' ,
'preLoginNameUsedAsUserName' ,
[ 'uid' => & $user ]
);
2014-06-06 18:36:10 +04:00
// FIXME: use HTTP error codes
2014-05-28 21:13:07 +04:00
try {
2014-10-20 21:05:48 +04:00
$this -> sendEmail ( $user );
2019-07-26 16:21:41 +03:00
} catch ( ResetPasswordException $e ) {
2019-01-14 23:05:52 +03:00
// Ignore the error since we do not want to leak this info
2019-07-26 16:21:41 +03:00
$this -> logger -> warning ( 'Could not send password reset email: ' . $e -> getMessage ());
} catch ( \Exception $e ) {
$this -> logger -> logException ( $e );
2014-05-28 21:13:07 +04:00
}
2014-06-06 18:36:10 +04:00
2017-04-14 14:42:40 +03:00
$response = new JSONResponse ( $this -> success ());
$response -> throttle ();
return $response ;
2014-05-28 21:13:07 +04:00
}
2014-06-06 18:36:10 +04:00
2014-05-28 21:13:07 +04:00
/**
* @ PublicPage
2014-10-20 21:05:48 +04:00
* @ param string $token
* @ param string $userId
* @ param string $password
* @ param boolean $proceed
* @ return array
2014-05-28 21:13:07 +04:00
*/
2014-10-20 21:05:48 +04:00
public function setPassword ( $token , $userId , $password , $proceed ) {
2017-05-11 17:46:43 +03:00
if ( $this -> config -> getSystemValue ( 'lost_password_link' , '' ) !== '' ) {
return $this -> error ( $this -> l10n -> t ( 'Password reset is disabled' ));
}
2016-09-29 17:38:29 +03:00
if ( $this -> encryptionManager -> isEnabled () && ! $proceed ) {
2018-08-10 17:12:34 +03:00
$encryptionModules = $this -> encryptionManager -> getEncryptionModules ();
foreach ( $encryptionModules as $module ) {
/** @var IEncryptionModule $instance */
$instance = call_user_func ( $module [ 'callback' ]);
// this way we can find out whether per-user keys are used or a system wide encryption key
if ( $instance -> needDetailedAccessList ()) {
return $this -> error ( '' , array ( 'encryption' => true ));
}
}
2014-10-20 21:05:48 +04:00
}
2014-05-28 21:13:07 +04:00
try {
2016-05-19 14:23:12 +03:00
$this -> checkPasswordResetToken ( $token , $userId );
2014-06-06 18:51:58 +04:00
$user = $this -> userManager -> get ( $userId );
2014-06-06 18:36:10 +04:00
2017-01-02 23:24:37 +03:00
\OC_Hook :: emit ( '\OC\Core\LostPassword\Controller\LostController' , 'pre_passwordReset' , array ( 'uid' => $userId , 'password' => $password ));
2014-07-24 14:50:39 +04:00
if ( ! $user -> setPassword ( $password )) {
2014-06-06 18:36:10 +04:00
throw new \Exception ();
2014-05-28 21:13:07 +04:00
}
2014-06-06 18:36:10 +04:00
2014-10-29 14:44:15 +03:00
\OC_Hook :: emit ( '\OC\Core\LostPassword\Controller\LostController' , 'post_passwordReset' , array ( 'uid' => $userId , 'password' => $password ));
2014-10-24 15:41:44 +04:00
2019-01-28 18:12:06 +03:00
$this -> twoFactorManager -> clearTwoFactorPending ( $userId );
2016-08-23 16:01:38 +03:00
$this -> config -> deleteUserValue ( $userId , 'core' , 'lostpassword' );
2017-07-24 13:17:53 +03:00
@ \OC :: $server -> getUserSession () -> unsetMagicInCookie ();
2018-05-20 13:51:50 +03:00
} catch ( HintException $e ){
return $this -> error ( $e -> getHint ());
2014-06-06 18:36:10 +04:00
} catch ( \Exception $e ){
2014-06-06 18:51:58 +04:00
return $this -> error ( $e -> getMessage ());
2014-05-28 21:13:07 +04:00
}
2014-06-06 18:36:10 +04:00
2018-06-19 18:02:20 +03:00
return $this -> success ([ 'user' => $userId ]);
2014-05-28 21:13:07 +04:00
}
2014-06-06 18:36:10 +04:00
2014-10-20 21:05:48 +04:00
/**
2017-03-28 21:39:36 +03:00
* @ param string $input
2019-07-26 16:21:41 +03:00
* @ throws ResetPasswordException
* @ throws \OCP\PreConditionNotMetException
2014-10-20 21:05:48 +04:00
*/
2017-03-28 21:39:36 +03:00
protected function sendEmail ( $input ) {
$user = $this -> findUserByIdOrMail ( $input );
$email = $user -> getEMailAddress ();
2014-06-06 18:36:10 +04:00
2014-05-28 21:13:07 +04:00
if ( empty ( $email )) {
2019-07-26 16:21:41 +03:00
throw new ResetPasswordException ( 'Could not send reset e-mail since there is no email for username ' . $input );
2014-05-28 21:13:07 +04:00
}
2014-06-06 18:36:10 +04:00
2016-08-28 15:22:29 +03:00
// Generate the token. It is stored encrypted in the database with the
// secret being the users' email address appended with the system secret.
// This makes the token automatically invalidate once the user changes
// their email address.
$token = $this -> secureRandom -> generate (
21 ,
2014-10-20 21:05:48 +04:00
ISecureRandom :: CHAR_DIGITS .
ISecureRandom :: CHAR_LOWER .
2016-08-28 15:22:29 +03:00
ISecureRandom :: CHAR_UPPER
);
$tokenValue = $this -> timeFactory -> getTime () . ':' . $token ;
2017-03-28 21:39:36 +03:00
$encryptedValue = $this -> crypto -> encrypt ( $tokenValue , $email . $this -> config -> getSystemValue ( 'secret' ));
$this -> config -> setUserValue ( $user -> getUID (), 'core' , 'lostpassword' , $encryptedValue );
2014-10-20 21:05:48 +04:00
2017-03-28 21:39:36 +03:00
$link = $this -> urlGenerator -> linkToRouteAbsolute ( 'core.lost.resetform' , array ( 'userId' => $user -> getUID (), 'token' => $token ));
2014-06-06 18:36:10 +04:00
2017-09-04 16:07:19 +03:00
$emailTemplate = $this -> mailer -> createEMailTemplate ( 'core.ResetPassword' , [
2017-08-24 19:02:37 +03:00
'link' => $link ,
]);
2017-04-12 01:24:58 +03:00
2017-09-15 11:59:11 +03:00
$emailTemplate -> setSubject ( $this -> l10n -> t ( '%s password reset' , [ $this -> defaults -> getName ()]));
2017-04-12 01:24:58 +03:00
$emailTemplate -> addHeader ();
$emailTemplate -> addHeading ( $this -> l10n -> t ( 'Password reset' ));
$emailTemplate -> addBodyText (
2018-02-15 14:18:51 +03:00
htmlspecialchars ( $this -> l10n -> t ( 'Click the following button to reset your password. If you have not requested the password reset, then ignore this email.' )),
2017-04-12 01:24:58 +03:00
$this -> l10n -> t ( 'Click the following link to reset your password. If you have not requested the password reset, then ignore this email.' )
);
$emailTemplate -> addBodyButton (
2018-02-15 14:18:51 +03:00
htmlspecialchars ( $this -> l10n -> t ( 'Reset your password' )),
2017-04-12 01:24:58 +03:00
$link ,
false
);
$emailTemplate -> addFooter ();
2014-06-06 18:36:10 +04:00
2014-05-28 21:13:07 +04:00
try {
2015-02-12 18:03:51 +03:00
$message = $this -> mailer -> createMessage ();
2017-03-28 21:39:36 +03:00
$message -> setTo ([ $email => $user -> getUID ()]);
2015-02-12 18:03:51 +03:00
$message -> setFrom ([ $this -> from => $this -> defaults -> getName ()]);
2017-09-15 12:01:21 +03:00
$message -> useTemplate ( $emailTemplate );
2015-02-12 18:03:51 +03:00
$this -> mailer -> send ( $message );
2014-05-28 21:13:07 +04:00
} catch ( \Exception $e ) {
2019-07-26 16:21:41 +03:00
// Log the exception and continue
$this -> logger -> logException ( $e );
2014-05-28 21:13:07 +04:00
}
}
2014-05-28 01:09:08 +04:00
2017-03-28 21:39:36 +03:00
/**
* @ param string $input
* @ return IUser
2019-07-26 16:21:41 +03:00
* @ throws ResetPasswordException
2017-03-28 21:39:36 +03:00
*/
protected function findUserByIdOrMail ( $input ) {
$user = $this -> userManager -> get ( $input );
if ( $user instanceof IUser ) {
2017-08-18 14:03:40 +03:00
if ( ! $user -> isEnabled ()) {
2019-07-26 16:21:41 +03:00
throw new ResetPasswordException ( 'User is disabled' );
2017-08-18 14:03:40 +03:00
}
2017-03-28 21:39:36 +03:00
return $user ;
}
2017-08-18 14:03:40 +03:00
2019-07-09 12:58:14 +03:00
$users = array_filter ( $this -> userManager -> getByEmail ( $input ), function ( IUser $user ) {
2018-08-18 17:51:59 +03:00
return $user -> isEnabled ();
});
2019-07-09 12:58:14 +03:00
if ( count ( $users ) === 1 ) {
return reset ( $users );
2017-03-28 21:39:36 +03:00
}
2019-07-26 16:21:41 +03:00
throw new ResetPasswordException ( 'Could not find user' );
2017-03-28 21:39:36 +03:00
}
2014-05-28 01:09:08 +04:00
}