View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.jetspeed.portlets.registration;
18  
19  import java.io.FileNotFoundException;
20  import java.io.IOException;
21  import java.security.Principal;
22  import java.util.ArrayList;
23  import java.util.Date;
24  import java.util.HashMap;
25  import java.util.Iterator;
26  import java.util.LinkedList;
27  import java.util.List;
28  import java.util.Locale;
29  import java.util.Map;
30  import java.util.ResourceBundle;
31  import java.util.Set;
32  import java.util.prefs.Preferences;
33  
34  import javax.portlet.ActionRequest;
35  import javax.portlet.ActionResponse;
36  import javax.portlet.PortletConfig;
37  import javax.portlet.PortletException;
38  import javax.portlet.PortletRequest;
39  import javax.portlet.PortletResponse;
40  import javax.portlet.RenderRequest;
41  import javax.portlet.RenderResponse;
42  
43  import org.apache.jetspeed.CommonPortletServices;
44  import org.apache.jetspeed.PortalReservedParameters;
45  import org.apache.jetspeed.administration.AdministrationEmailException;
46  import org.apache.jetspeed.administration.PortalAdministration;
47  import org.apache.jetspeed.locator.JetspeedTemplateLocator;
48  import org.apache.jetspeed.locator.LocatorDescriptor;
49  import org.apache.jetspeed.locator.TemplateDescriptor;
50  import org.apache.jetspeed.locator.TemplateLocatorException;
51  import org.apache.jetspeed.request.RequestContext;
52  import org.apache.jetspeed.security.PasswordCredential;
53  import org.apache.jetspeed.security.SecurityException;
54  import org.apache.jetspeed.security.User;
55  import org.apache.jetspeed.security.UserManager;
56  import org.apache.jetspeed.security.UserPrincipal;
57  import org.apache.portals.bridges.velocity.AbstractVelocityMessagingPortlet;
58  import org.apache.portals.gems.util.ValidationHelper;
59  import org.apache.velocity.context.Context;
60  
61  /***
62   * This portlet allows a logged on user to change its password.
63   *
64   * @author <a href="mailto:taylor@apache.org">David Sean Taylor</a>
65   * @author <a href="mailto:chris@bluesunrise.com">Chris Schaefer</a>
66   * @version $Id: $
67   */
68  public class ForgottenPasswordPortlet extends AbstractVelocityMessagingPortlet
69  {
70      private PortalAdministration admin;
71  
72      private UserManager userManager;
73  
74      // Request Params 
75      private static final String RP_EMAIL_ADDRESS = "email";
76  
77      // Messages 
78      private static final String MSG_MESSAGE = "MSG";
79      private static final String MSG_CHANGEDPW_MSG = "CH_PWD_MSD";
80  
81      // Context Variables
82      private static final String CTX_EMAIL_ADDRESS = "email";
83  
84      private static final String CTX_RETURN_URL = "returnURL";
85  
86      private static final String CTX_NEW_PASSWORD = "password";
87  
88      private static final String CTX_USER_NAME = "username";
89  
90      private static final String CTX_MESSAGE = "MSG";
91  
92      private static final String CTX_CHANGEDPW_MSG = "updatedPWMsg";
93  
94      // Init Parameter Constants
95      private static final String IP_REDIRECT_PATH = "redirectPath";
96  
97      private static final String IP_RETURN_URL = "returnURL";
98  
99      private static final String IP_TEMPLATE_LOCATION = "emailTemplateLocation";
100 
101     private static final String IP_TEMPLATE_NAME = "emailTemplateName";
102 
103     // Resource Bundle
104     private static final String RB_EMAIL_SUBJECT = "email.subject.forgotten.password";
105 
106     private static final String PATH_SEPARATOR = "/";
107 
108     /*** email template to use for merging */
109     private String templateLocation;
110 
111     private String templateName;
112 
113     private JetspeedTemplateLocator templateLocator;
114 
115     /*** servlet path of the return url to be printed and href'd in email template */
116     private String returnUrlPath;
117 
118     /*** path where to redirect to after pressing submit on the form */
119     private String redirectPath;
120 
121     /*** localized emailSubject */
122     private String emailSubject = null;
123     
124     public void init(PortletConfig config) throws PortletException
125     {
126         super.init(config);
127         admin = (PortalAdministration) getPortletContext().getAttribute(
128                 CommonPortletServices.CPS_PORTAL_ADMINISTRATION);
129         if (null == admin) { throw new PortletException(
130                 "Failed to find the Portal Administration on portlet initialization"); }
131         userManager = (UserManager) getPortletContext().getAttribute(
132                 CommonPortletServices.CPS_USER_MANAGER_COMPONENT);
133         if (null == userManager) { throw new PortletException(
134                 "Failed to find the User Manager on portlet initialization"); }
135 
136         this.returnUrlPath = config.getInitParameter(IP_RETURN_URL);
137         this.redirectPath = config.getInitParameter(IP_REDIRECT_PATH);
138         this.templateLocation = config.getInitParameter(IP_TEMPLATE_LOCATION);
139         if (templateLocation == null)
140         {
141             templateLocation = "/WEB-INF/view/userreg/";
142         }
143         templateLocation = getPortletContext().getRealPath(templateLocation);
144         this.templateName = config.getInitParameter(IP_TEMPLATE_NAME);
145         if (templateName == null)
146         {
147             templateName = "forgottenPasswdEmail.vm";
148         }
149         
150         ArrayList roots = new ArrayList(1);
151         roots.add(templateLocation);
152 
153         try
154         {
155             templateLocator = new JetspeedTemplateLocator(roots, "email", getPortletContext().getRealPath("/"));
156             templateLocator.start();
157         }
158         catch (FileNotFoundException e)
159         {
160             throw new PortletException("Could not start the template locator.", e);
161         }
162 
163         
164     }
165 
166     private boolean isValidGUID(String guid)
167     {
168         Map map = admin.getNewLoginInfo(guid);
169         
170         if (map != null) { return true; }
171         return false;
172     }
173 
174     private boolean updatePasswordFromGUID(String guid)
175     {
176         Map map = admin.getNewLoginInfo(guid);
177         
178         String userName = (String) map.get("user.name");
179         String newPassword = (String) map.get("password");
180 
181         // Here's where a break should be.   The following code should be put into the RETURN portlet
182         try
183         {
184             userManager.setPassword(userName, null, newPassword);
185             userManager.setPasswordUpdateRequired(userName, true);
186             // if we got here stuff is changed... removed the key from the map
187             admin.removeNewLoginInfo(guid);
188         } catch (SecurityException e)
189         {
190             return false;
191         }
192         return true;
193     }
194 
195     public void doView(RenderRequest request, RenderResponse response)
196             throws PortletException, IOException
197     {
198         response.setContentType("text/html");
199         Context context = getContext(request);
200         String email = request.getParameter(RP_EMAIL_ADDRESS);
201         String guid = request.getParameter("guid");
202 
203         ResourceBundle resource = getPortletConfig().getResourceBundle(request.getLocale());
204 
205         if (guid != null) 
206         {
207             if(isValidGUID(guid)) 
208             {
209                 try
210                 {
211                     updatePasswordFromGUID(guid);
212                     context
213                             .put(CTX_CHANGEDPW_MSG, resource.getString("forgotten.successful_pw_update"));
214                 } catch (Exception e)
215                 {
216                     context.put(CTX_MESSAGE,resource.getString("forgotten.unable_to_update_pw"));
217                 }
218             } else {
219                 // invalid GUID
220                 context
221                 .put(CTX_CHANGEDPW_MSG,resource.getString("forgotten.password_update_link_invalid"));
222             }
223         } else {
224             // might be returning from initial request
225             context.put(CTX_CHANGEDPW_MSG,consumeRenderMessage(request, MSG_CHANGEDPW_MSG));
226         }
227         context.put(CTX_EMAIL_ADDRESS, email);
228         context.put(CTX_MESSAGE, consumeRenderMessage(request, MSG_MESSAGE));
229         super.doView(request, response);
230     }
231 
232     public static String makeGUID(String user, String newpw)
233     {
234         // This is a quicky version
235         long num = (long) user.hashCode() + (long) newpw.hashCode(); //  Possible collisions here...
236         long d = new Date().getTime();
237         long val = num * d;
238         String retval = Long.toHexString(val);
239         return retval;
240     }
241 
242     public void processAction(ActionRequest request, ActionResponse response)
243             throws PortletException, IOException
244     {
245         List errors = new LinkedList();
246 
247         String email = request.getParameter(RP_EMAIL_ADDRESS);
248         Locale locale = request.getLocale();
249 
250         ResourceBundle resource = getPortletConfig().getResourceBundle(locale);
251 
252         // validation
253         if (!ValidationHelper.isEmailAddress(email, true, 80))
254         {
255             errors.add(resource.getString("forgotten.invalid_email_format_entered"));
256         }
257 
258         if (errors.size() > 0)
259         {
260             publishRenderMessage(request, MSG_MESSAGE, errors);
261             return;
262         }
263 
264         User user = null;
265         try
266         {
267             user = admin.lookupUserFromEmail(email);
268         } catch (Exception e)
269         {
270             publishRenderMessage(
271                     request,
272                     MSG_MESSAGE,
273                     makeMessage(resource.getString("forgotten.email_address_not_found")));
274             return;
275         }
276 
277         try
278         {
279             String userName = getUserName(user);
280 
281             String newPassword = admin.generatePassword();
282 
283             String urlGUID = makeGUID(userName, newPassword);
284 
285             Preferences pref = user.getUserAttributes();
286             String[] keys = pref.keys();
287             Map userAttributes = new HashMap();
288             if (keys != null)
289             {
290                 for (int ix = 0; ix < keys.length; ix++)
291                 {
292                     // TODO: how the hell do i tell the pref type
293                     // ASSuming they are all strings (sigh)
294                     userAttributes.put(keys[ix], pref.get(keys[ix], ""));
295                 }
296             }
297             // special attributes
298             userAttributes.put(CTX_RETURN_URL, generateReturnURL(request,
299                     response, urlGUID));
300             userAttributes.put(CTX_NEW_PASSWORD, newPassword);
301             userAttributes.put(CTX_USER_NAME, userName);
302 
303             String templ = getTemplatePath(request, response);
304             
305             if (templ == null) 
306             { 
307                 throw new Exception("email template not available"); 
308             }
309             admin.sendEmail(this.getPortletConfig(), email,
310                     getEmailSubject(request),templ, userAttributes);
311 
312             //TODO this is currently hacked with a hashmap... needs to move to either a DB table
313             // or to some sort of credential
314             Map map = new HashMap();
315             map.put("user.name",userName);
316             map.put("password",newPassword);
317             admin.putNewLoginInfo(urlGUID, map);
318 
319             publishRenderMessage(
320                     request,
321                     MSG_CHANGEDPW_MSG,
322                     makeMessage(resource.getString("an_email_has_been_sent")));
323             
324             response.sendRedirect(generateRedirectURL(request, response));
325         } 
326         catch (AdministrationEmailException e)
327         {
328             publishRenderMessage(request, CTX_MESSAGE, makeMessage(e
329                     .getMessage()));
330         } 
331         catch (Exception e)
332         {
333             publishRenderMessage(request, CTX_MESSAGE,
334                     makeMessage(resource.getString("failed_to_send") + e.toString()));
335         }
336 
337     }
338 
339     protected String getEmailSubject(PortletRequest request)
340     {
341         ResourceBundle resource = getPortletConfig().getResourceBundle(
342                 request.getLocale());
343         try
344         {
345             this.emailSubject = resource.getString(RB_EMAIL_SUBJECT);
346         } catch (Exception e)
347         {
348             //TODO  report missing resource somehow
349         }
350         if (this.emailSubject == null)
351                 this.emailSubject = "Password Notification";
352         return this.emailSubject;
353     }
354 
355     protected String generateReturnURL(PortletRequest request,
356                                        PortletResponse response,
357                                        String urlGUID)
358     {
359         String fullPath = this.returnUrlPath + "?guid=" + urlGUID; 
360         // NOTE: getPortalURL will encode the fullPath for us
361         String url = admin.getPortalURL(request, response, fullPath);
362         return url;
363     }
364 
365     protected String generateRedirectURL(PortletRequest request,
366                                          PortletResponse response)
367                                          
368     {
369         return admin.getPortalURL(request, response, this.redirectPath);
370     }
371     
372     protected String getUserName(User user)
373     {
374         Principal principal = null;
375         Iterator principals = user.getSubject().getPrincipals().iterator();
376         while (principals.hasNext())
377         {
378             Object o = principals.next();
379             if (o instanceof UserPrincipal)
380             {
381                 principal = (Principal) o;
382                 return principal.toString();
383             }
384 
385         }
386         return null;
387     }
388 
389     protected String getPassword(User user)
390     {
391         PasswordCredential credential = null;
392 
393         Set credentials = user.getSubject().getPrivateCredentials();
394         Iterator iter = credentials.iterator();
395         while (iter.hasNext())
396         {
397             Object o = iter.next();
398             if (o instanceof PasswordCredential)
399             {
400                 credential = (PasswordCredential) o;
401                 char[] charar = credential.getPassword();
402 
403                 return new String(charar);
404             }
405         }
406         return null;
407     }
408 
409     protected List makeMessage(String msg)
410     {
411         List errors = new LinkedList();
412         errors.add(msg);
413         return errors;
414     }
415     
416     protected String getTemplatePath(ActionRequest request, ActionResponse response)
417     {
418         if (templateLocator == null)
419         {
420             return templateLocation + PATH_SEPARATOR + templateName;
421         }
422 
423         RequestContext requestContext = (RequestContext) request
424                 .getAttribute(PortalReservedParameters.REQUEST_CONTEXT_ATTRIBUTE);
425         Locale locale = request.getLocale();
426 
427         try
428         {
429             LocatorDescriptor locator = templateLocator.createLocatorDescriptor("email");
430             locator.setName(templateName);
431             locator.setMediaType(requestContext.getMediaType());
432             locator.setLanguage(locale.getLanguage());
433             locator.setCountry(locale.getCountry());
434             TemplateDescriptor template = templateLocator.locateTemplate(locator);
435 
436             return template.getAppRelativePath();
437         }
438         catch (TemplateLocatorException e)
439         {
440             return templateLocation + PATH_SEPARATOR + templateName;
441         }
442     }
443 
444 }