1<?php
2/*
3 * Gallery - a web based photo album viewer and editor
4 * Copyright (C) 2000-2008 Bharat Mediratta
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or (at
9 * your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA  02110-1301, USA.
19 */
20
21GalleryCoreApi::requireOnce('modules/core/classes/helpers/UserRecoverPasswordHelper_simple.class');
22
23/**
24 * This controller will handle the recovery of passwords that have been lost or forgotten
25 * by the user.
26 * @package GalleryCore
27 * @subpackage UserInterface
28 * @author Jay Rossiter <cryptographite@users.sf.net>
29 * @version $Revision: 20996 $
30 */
31class UserRecoverPasswordController extends GalleryController {
32    /**
33     * ValidationPlugin instances to use when handling this request.  Only used by test code.
34     *
35     * @var array (pluginId => ValidationPlugin) $_pluginInstances
36     * @access private
37     */
38    var $_pluginInstances;
39
40    /**
41     * Tests can use this method to hardwire a specific set of plugin instances to use.
42     * This avoids situations where some of the option instances will do unpredictable
43     * things and derail the tests.
44     *
45     * @param array $pluginInstances of GalleryValidationPlugin
46     */
47    function setPluginInstances($pluginInstances) {
48	$this->_pluginInstances = $pluginInstances;
49    }
50
51    /**
52     * @see GalleryController::handleRequest
53     */
54    function handleRequest($form) {
55	global $gallery;
56
57	$status = $error = $results = array();
58
59	$phpVm = $gallery->getPhpVm();
60	if (isset($form['action']['recover'])) {
61	    $form['userName'] = is_string($form['userName']) ? $form['userName'] : null;
62	    if (empty($form['userName'])) {
63		$error[] = 'form[error][userName][missing]';
64	    }
65
66	    /* If no errors have been detected, let the validation plugins do their work */
67	    if (empty($error)) {
68		if (isset($this->_pluginInstances)) {
69		    $pluginInstances = $this->_pluginInstances;
70		} else {
71		    /* Get all the validation plugins */
72		    list ($ret, $pluginInstances) =
73			GalleryCoreApi::getAllFactoryImplementationIds('GalleryValidationPlugin');
74		    if ($ret) {
75			return array($ret, null);
76		    }
77
78		    foreach (array_keys($pluginInstances) as $pluginId) {
79			list ($ret, $pluginInstances[$pluginId]) =
80			    GalleryCoreApi::newFactoryInstanceById('GalleryValidationPlugin',
81								   $pluginId);
82			if ($ret) {
83			    return array($ret, null);
84			}
85		    }
86		}
87
88		/* Let each plugin do its verification */
89		foreach ($pluginInstances as $plugin) {
90		    list ($ret, $pluginErrors, $continue) = $plugin->performValidation($form);
91		    if ($ret) {
92			return array($ret, null);
93		    }
94
95		    $error = array_merge($error, $pluginErrors);
96		    if (!$continue) {
97			break;
98		    }
99		}
100	    }
101
102	    /*
103	     * Still no errors?  Check the DB for a previous request and then
104	     * update, reject or add based on the results.
105	     */
106	    $shouldSendEmail = false;
107	    if (empty($error)) {
108		list ($ret, $user) = GalleryCoreApi::fetchUserByUsername($form['userName']);
109		if ($ret && !($ret->getErrorCode() & ERROR_MISSING_OBJECT)) {
110		    return array($ret, null);
111		}
112
113		if (isset($user) && $user->getEmail() != '') {
114		    /* Generate a unique auth string based on userName, time of request and IP */
115		    $authString = $this->_generateAuthString();
116
117		    /* Generate the request expiration: Now + 7 Days */
118		    $requestExpires = mktime(date('G'), date('i'), date('s'),
119					     date('m'), date('d')+7, date('Y'));
120
121		    /*
122		     * Check the database to see if a previous request.
123		     * If a request exists, check the timestamp to see if a new
124		     * request can be generated, or if they will be denied
125		     * because the window is too small.
126		     */
127		    list ($ret, $lastRequest) = UserRecoverPasswordHelper_simple::getRequestExpires(
128			$user->getUserName(), null);
129		    if ($ret) {
130			return array($ret, null);
131		    }
132
133		    /*
134		     * This request was made less than 20 minutes ago.  Don't update the auth
135		     * string.  We'll silently succeed to thwart phishing attempts.
136		     */
137		    if (!empty($lastRequest)) {
138			if (($lastRequest - (7 * 24 * 60 * 60) + (20 * 60)) < time()) {
139			    $ret = GalleryCoreApi::updateMapEntry(
140				'GalleryRecoverPasswordMap',
141				array('userName' => $user->getUserName()),
142				array('authString' => $authString,
143				      'requestExpires' => $requestExpires));
144			    $shouldSendEmail = true;
145			}
146		    } else {
147			/*
148			 * Add the map entry before sending email to the user -
149			 * We don't want to send them mail if the data never gets into the DB
150			 */
151			$ret = GalleryCoreApi::addMapEntry(
152			    'GalleryRecoverPasswordMap',
153			    array('userName' => $form['userName'],
154				  'authString' => $authString,
155				  'requestExpires' => $requestExpires));
156			if ($ret) {
157			    return array($ret, null);
158			}
159			$shouldSendEmail = true;
160		    }
161
162		    if (empty($error) && $shouldSendEmail) {
163			/* Generate baseUrl and recoverUrl for the email template */
164			$generator =& $gallery->getUrlGenerator();
165			$baseUrl = $generator->generateUrl(array(),
166					array('forceFullUrl' => true, 'htmlEntities' => false,
167					      'forceSessionId' => false));
168			$recoverUrl = $generator->generateUrl(
169					array('view' => 'core.UserAdmin',
170					      'subView' => 'core.UserRecoverPasswordConfirm',
171					      'userName' => $user->getUserName(),
172					      'authString' => $authString),
173					array('forceFullUrl' => true, 'htmlEntities' => false,
174					      'forceSessionId' => false));
175
176			/* email template data */
177			$tplData = array('name' => $user->getfullName(),
178					 'baseUrl' => $baseUrl,
179					 'ip' => GalleryUtilities::getRemoteHostAddress(),
180					 'date' => date('r'),
181					 'userName' => $user->getUserName(),
182					 'recoverUrl' => $recoverUrl,
183					 );
184
185			/* Load core for translation */
186			list ($ret, $module) = GalleryCoreApi::loadPlugin('module', 'core');
187			if ($ret) {
188			    return array($ret, null);
189			}
190
191			/* Send the user email based on our confirmation template */
192			$ret = GalleryCoreApi::sendTemplatedEmail(
193			    'modules/core/templates/UserRecoverPasswordEmail.tpl',
194			    $tplData, '', $user->getEmail(),
195			    $module->translate('Password Recovery'));
196			if ($ret) {
197			    return array($ret, null);
198			}
199		    }
200
201		    /* Set the recovered info flag */
202		    $status['requestSent'] = 1;
203		} else {
204		    /* Silently succeed; we don't reward phishing attempts */
205		    /* Set the recovered info flag */
206		    $status['requestSent'] = 1;
207		}
208	    }
209	} else if (isset($form['action']['cancel'])) {
210	    $results['return'] = 1;
211	}
212
213	if (empty($subView)) {
214	    $subView = 'core.UserRecoverPassword';
215	}
216
217	if (empty($error)) {
218	    $results['redirect']['view'] = 'core.UserAdmin';
219	    $results['redirect']['subView'] = $subView;
220	} else {
221	    $results['delegate']['view'] = 'core.UserAdmin';
222	    $results['delegate']['subView'] = $subView;
223	}
224
225	$results['status'] = $status;
226	$results['error'] = $error;
227
228	return array(null, $results);
229    }
230
231    /**
232     * Generate the authorization string used for login.txt
233     * @access private
234     */
235    function _generateAuthString() {
236        GalleryCoreApi::requireOnce('lib/joomla/crypt.php');
237        $j = new JCrypt();
238        return md5($j->genRandomBytes(32));
239    }
240}
241
242/**
243 * This view shows information about password recovery
244 */
245class UserRecoverPasswordView extends GalleryView {
246
247    /**
248     * @see GalleryView::loadTemplate
249     */
250    function loadTemplate(&$template, &$form) {
251	global $gallery;
252
253	if ($form['formName'] == 'UserRecoverPassword') {
254	    if (empty($form['userName'])) {
255		$form['error']['userName']['missing'] = 1;
256	    }
257	} else {
258	    $form['userName'] = '';
259	    $form['formName'] = 'UserRecoverPassword';
260	}
261
262	$UserRecoverPassword = array();
263
264	/* Get all the login plugins */
265	list ($ret, $allPluginIds) =
266	    GalleryCoreApi::getAllFactoryImplementationIds('GalleryValidationPlugin');
267	if ($ret) {
268	    return array($ret, null);
269	}
270
271	/* Let each plugin load its template data */
272	$UserRecoverPassword['plugins'] = array();
273	foreach (array_keys($allPluginIds) as $pluginId) {
274	    list ($ret, $plugin) =
275		GalleryCoreApi::newFactoryInstanceById('GalleryValidationPlugin', $pluginId);
276	    if ($ret) {
277		return array($ret, null);
278	    }
279
280	    list ($ret, $data['file'], $data['l10Domain']) = $plugin->loadTemplate($form);
281	    if ($ret) {
282		return array($ret, null);
283	    }
284
285	    if (isset($data['file'])) {
286		$UserRecoverPassword['plugins'][] = $data;
287	    }
288	}
289
290	$template->setVariable('UserRecoverPassword', $UserRecoverPassword);
291	$template->setVariable('controller', 'core.UserRecoverPassword');
292	return array(null, array('body' => 'modules/core/templates/UserRecoverPassword.tpl'));
293    }
294}
295?>
296