001: /*
002: JSPWiki - a JSP-based WikiWiki clone.
003:
004: Copyright (C) 2001-2005 Janne Jalkanen (Janne.Jalkanen@iki.fi)
005:
006: This program is free software; you can redistribute it and/or modify
007: it under the terms of the GNU Lesser General Public License as published by
008: the Free Software Foundation; either version 2.1 of the License, or
009: (at your option) any later version.
010:
011: This program is distributed in the hope that it will be useful,
012: but WITHOUT ANY WARRANTY; without even the implied warranty of
013: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: GNU Lesser General Public License for more details.
015:
016: You should have received a copy of the GNU Lesser General Public License
017: along with this program; if not, write to the Free Software
018: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: */
020: package com.ecyrd.jspwiki.auth;
021:
022: import java.security.Permission;
023: import java.security.Principal;
024: import java.text.MessageFormat;
025: import java.util.*;
026:
027: import javax.mail.MessagingException;
028: import javax.mail.internet.AddressException;
029: import javax.servlet.http.HttpServletRequest;
030:
031: import org.apache.log4j.Logger;
032:
033: import com.ecyrd.jspwiki.*;
034: import com.ecyrd.jspwiki.auth.permissions.AllPermission;
035: import com.ecyrd.jspwiki.auth.permissions.WikiPermission;
036: import com.ecyrd.jspwiki.auth.user.AbstractUserDatabase;
037: import com.ecyrd.jspwiki.auth.user.DuplicateUserException;
038: import com.ecyrd.jspwiki.auth.user.UserDatabase;
039: import com.ecyrd.jspwiki.auth.user.UserProfile;
040: import com.ecyrd.jspwiki.event.WikiEventListener;
041: import com.ecyrd.jspwiki.event.WikiEventManager;
042: import com.ecyrd.jspwiki.event.WikiSecurityEvent;
043: import com.ecyrd.jspwiki.filters.PageFilter;
044: import com.ecyrd.jspwiki.filters.SpamFilter;
045: import com.ecyrd.jspwiki.i18n.InternationalizationManager;
046: import com.ecyrd.jspwiki.rpc.RPCCallable;
047: import com.ecyrd.jspwiki.rpc.json.JSONRPCManager;
048: import com.ecyrd.jspwiki.ui.InputValidator;
049: import com.ecyrd.jspwiki.util.ClassUtil;
050: import com.ecyrd.jspwiki.util.MailUtil;
051: import com.ecyrd.jspwiki.workflow.*;
052:
053: /**
054: * Provides a facade for obtaining user information.
055: * @author Janne Jalkanen
056: * @author Andrew Jaquith
057: * @since 2.3
058: */
059: public final class UserManager {
060: private static final String USERDATABASE_PACKAGE = "com.ecyrd.jspwiki.auth.user";
061: private static final String SESSION_MESSAGES = "profile";
062: private static final String PARAM_EMAIL = "email";
063: private static final String PARAM_FULLNAME = "fullname";
064: private static final String PARAM_PASSWORD = "password";
065: private static final String PARAM_LOGINNAME = "loginname";
066: private static final String UNKNOWN_CLASS = "<unknown>";
067:
068: private WikiEngine m_engine;
069:
070: private static Logger log = Logger.getLogger(UserManager.class);
071:
072: /** Message key for the "save profile" message. */
073: public static final String SAVE_APPROVER = "workflow.createUserProfile";
074: private static final String PROP_DATABASE = "jspwiki.userdatabase";
075: protected static final String SAVE_TASK_MESSAGE_KEY = "task.createUserProfile";
076: protected static final String SAVED_PROFILE = "userProfile";
077: protected static final String SAVE_DECISION_MESSAGE_KEY = "decision.createUserProfile";
078: protected static final String FACT_SUBMITTER = "fact.submitter";
079: protected static final String PREFS_LOGIN_NAME = "prefs.loginname";
080: protected static final String PREFS_FULL_NAME = "prefs.fullname";
081: protected static final String PREFS_EMAIL = "prefs.email";
082:
083: // private static final String PROP_ACLMANAGER = "jspwiki.aclManager";
084:
085: /** Associateds wiki sessions with profiles */
086: private final Map m_profiles = new WeakHashMap();
087:
088: /** The user database loads, manages and persists user identities */
089: private UserDatabase m_database;
090:
091: private boolean m_useJAAS = true;
092:
093: /**
094: * Constructs a new UserManager instance.
095: */
096: public UserManager() {
097: }
098:
099: /**
100: * Initializes the engine for its nefarious purposes.
101: * @param engine the current wiki engine
102: * @param props the wiki engine initialization properties
103: */
104: public final void initialize(WikiEngine engine, Properties props) {
105: m_engine = engine;
106:
107: m_useJAAS = AuthenticationManager.SECURITY_JAAS.equals(props
108: .getProperty(AuthenticationManager.PROP_SECURITY,
109: AuthenticationManager.SECURITY_JAAS));
110:
111: // Attach the PageManager as a listener
112: // TODO: it would be better if we did this in PageManager directly
113: addWikiEventListener(engine.getPageManager());
114:
115: JSONRPCManager.registerGlobalObject("users",
116: new JSONUserModule(), new AllPermission(null));
117: }
118:
119: /**
120: * Returns the UserDatabase employed by this WikiEngine. The UserDatabase is
121: * lazily initialized by this method, if it does not exist yet. If the
122: * initialization fails, this method will use the inner class
123: * DummyUserDatabase as a default (which is enough to get JSPWiki running).
124: * @return the dummy user database
125: * @since 2.3
126: */
127: public final UserDatabase getUserDatabase() {
128: // FIXME: Must not throw RuntimeException, but something else.
129: if (m_database != null) {
130: return m_database;
131: }
132:
133: if (!m_useJAAS) {
134: m_database = new DummyUserDatabase();
135: return m_database;
136: }
137:
138: String dbClassName = UNKNOWN_CLASS;
139:
140: try {
141: dbClassName = WikiEngine.getRequiredProperty(m_engine
142: .getWikiProperties(), PROP_DATABASE);
143:
144: log.info("Attempting to load user database class "
145: + dbClassName);
146: Class dbClass = ClassUtil.findClass(USERDATABASE_PACKAGE,
147: dbClassName);
148: m_database = (UserDatabase) dbClass.newInstance();
149: m_database.initialize(m_engine, m_engine
150: .getWikiProperties());
151: log.info("UserDatabase initialized.");
152: } catch (NoRequiredPropertyException e) {
153: log
154: .error("You have not set the '"
155: + PROP_DATABASE
156: + "'. You need to do this if you want to enable user management by JSPWiki.");
157: } catch (ClassNotFoundException e) {
158: log.error("UserDatabase class " + dbClassName
159: + " cannot be found", e);
160: } catch (InstantiationException e) {
161: log.error("UserDatabase class " + dbClassName
162: + " cannot be created", e);
163: } catch (IllegalAccessException e) {
164: log
165: .error(
166: "You are not allowed to access this user database class",
167: e);
168: } finally {
169: if (m_database == null) {
170: log
171: .info("I could not create a database object you specified (or didn't specify), so I am falling back to a default.");
172: m_database = new DummyUserDatabase();
173: }
174: }
175:
176: return m_database;
177: }
178:
179: /**
180: * <p>Retrieves the {@link com.ecyrd.jspwiki.auth.user.UserProfile}for the
181: * user in a wiki session. If the user is authenticated, the UserProfile
182: * returned will be the one stored in the user database; if one does not
183: * exist, a new one will be initialized and returned. If the user is
184: * anonymous or asserted, the UserProfile will <i>always</i> be newly
185: * initialized to prevent spoofing of identities. If a UserProfile needs to
186: * be initialized, its
187: * {@link com.ecyrd.jspwiki.auth.user.UserProfile#isNew()} method will
188: * return <code>true</code>, and its login name will will be set
189: * automatically if the user is authenticated. Note that this method does
190: * not modify the retrieved (or newly created) profile otherwise; other
191: * fields in the user profile may be <code>null</code>.</p>
192: * <p>If a new UserProfile was created, but its
193: * {@link com.ecyrd.jspwiki.auth.user.UserProfile#isNew()} method returns
194: * <code>false</code>, this method throws an {@link IllegalStateException}.
195: * This is meant as a quality check for UserDatabase providers;
196: * it should only be thrown if the implementation is faulty.</p>
197: * @param session the wiki session, which may not be <code>null</code>
198: * @return the user's profile, which will be newly initialized if the user
199: * is anonymous or asserted, or if the user cannot be found in the user
200: * database
201: */
202: public final UserProfile getUserProfile(WikiSession session) {
203: // Look up cached user profile
204: UserProfile profile = (UserProfile) m_profiles.get(session);
205: boolean newProfile = profile == null;
206: Principal user = null;
207:
208: // If user is authenticated, figure out if this is an existing profile
209: if (session.isAuthenticated()) {
210: user = session.getUserPrincipal();
211: try {
212: profile = getUserDatabase().find(user.getName());
213: newProfile = false;
214: } catch (NoSuchPrincipalException e) {
215: }
216: }
217:
218: if (newProfile) {
219: profile = getUserDatabase().newProfile();
220: if (user != null) {
221: profile.setLoginName(user.getName());
222: }
223: if (!profile.isNew()) {
224: throw new IllegalStateException(
225: "New profile should be marked 'new'. Check your UserProfile implementation.");
226: }
227: }
228:
229: // Stash the profile for next time
230: m_profiles.put(session, profile);
231: return profile;
232: }
233:
234: /**
235: * <p>
236: * Saves the {@link com.ecyrd.jspwiki.auth.user.UserProfile}for the user in
237: * a wiki session. This method verifies that a user profile to be saved
238: * doesn't collide with existing profiles; that is, the login name
239: * or full name is already used by another profile. If the profile
240: * collides, a <code>DuplicateUserException</code> is thrown. After saving
241: * the profile, the user database changes are committed, and the user's
242: * credential set is refreshed; if custom authentication is used, this means
243: * the user will be automatically be logged in.
244: * </p>
245: * <p>
246: * When the user's profile is saved succcessfully, this method fires a
247: * {@link WikiSecurityEvent#PROFILE_SAVE} event with the WikiSession as the
248: * source and the UserProfile as target. For existing profiles, if the
249: * user's full name changes, this method also fires a "name changed"
250: * event ({@link WikiSecurityEvent#PROFILE_NAME_CHANGED}) with the
251: * WikiSession as the source and an array containing the old and new
252: * UserProfiles, respectively. The <code>NAME_CHANGED</code> event allows
253: * the GroupManager and PageManager can change group memberships and
254: * ACLs if needed.
255: * </p>
256: * <p>
257: * Note that WikiSessions normally attach event listeners to the
258: * UserManager, so changes to the profile will automatically cause the
259: * correct Principals to be reloaded into the current WikiSession's Subject.
260: * </p>
261: * @param session the wiki session, which may not be <code>null</code>
262: * @param profile the user profile, which may not be <code>null</code>
263: * @throws DuplicateUserException if the proposed profile's login name or full name collides with another
264: * @throws WikiException if the save fails for some reason. If the current user does not have
265: * permission to save the profile, this will be a {@link com.ecyrd.jspwiki.auth.WikiSecurityException};
266: * if if the user profile must be approved before it can be saved, it will be a
267: * {@link com.ecyrd.jspwiki.workflow.DecisionRequiredException}. All other WikiException
268: * indicate a condition that is not normal is probably due to mis-configuration
269: */
270: public final void setUserProfile(WikiSession session,
271: UserProfile profile) throws DuplicateUserException,
272: WikiException {
273: // Verify user is allowed to save profile!
274: Permission p = new WikiPermission(
275: m_engine.getApplicationName(),
276: WikiPermission.EDIT_PROFILE_ACTION);
277: if (!m_engine.getAuthorizationManager().checkPermission(
278: session, p)) {
279: throw new WikiSecurityException(
280: "You are not allowed to save wiki profiles.");
281: }
282:
283: // Check if profile is new, and see if container allows creation
284: boolean newProfile = profile.isNew();
285:
286: // Check if another user profile already has the fullname or loginname
287: UserProfile oldProfile = getUserProfile(session);
288: boolean nameChanged = (oldProfile == null || oldProfile
289: .getFullname() == null) ? false
290: : !(oldProfile.getFullname().equals(
291: profile.getFullname()) && oldProfile
292: .getLoginName().equals(profile.getLoginName()));
293: UserProfile otherProfile;
294: try {
295: otherProfile = getUserDatabase().findByLoginName(
296: profile.getLoginName());
297: if (otherProfile != null
298: && !otherProfile.equals(oldProfile)) {
299: throw new DuplicateUserException("The login name '"
300: + profile.getLoginName()
301: + "' is already taken.");
302: }
303: } catch (NoSuchPrincipalException e) {
304: }
305: try {
306: otherProfile = getUserDatabase().findByFullName(
307: profile.getFullname());
308: if (otherProfile != null
309: && !otherProfile.equals(oldProfile)) {
310: throw new DuplicateUserException("The full name '"
311: + profile.getFullname() + "' is already taken.");
312: }
313: } catch (NoSuchPrincipalException e) {
314: }
315:
316: // For new accounts, create approval workflow for user profile save.
317: if (newProfile && oldProfile != null && oldProfile.isNew()) {
318: WorkflowBuilder builder = WorkflowBuilder
319: .getBuilder(m_engine);
320: Principal submitter = session.getUserPrincipal();
321: Task completionTask = new SaveUserProfileTask(m_engine);
322:
323: // Add user profile attribute as Facts for the approver (if required)
324: boolean hasEmail = profile.getEmail() != null;
325: Fact[] facts = new Fact[hasEmail ? 4 : 3];
326: facts[0] = new Fact(PREFS_FULL_NAME, profile.getFullname());
327: facts[1] = new Fact(PREFS_LOGIN_NAME, profile
328: .getLoginName());
329: facts[2] = new Fact(FACT_SUBMITTER, submitter.getName());
330: if (hasEmail) {
331: facts[3] = new Fact(PREFS_EMAIL, profile.getEmail());
332: }
333: Workflow workflow = builder.buildApprovalWorkflow(
334: submitter, SAVE_APPROVER, null,
335: SAVE_DECISION_MESSAGE_KEY, facts, completionTask,
336: null);
337:
338: workflow.setAttribute(SAVED_PROFILE, profile);
339: m_engine.getWorkflowManager().start(workflow);
340:
341: boolean approvalRequired = workflow.getCurrentStep() instanceof Decision;
342:
343: // If the profile requires approval, redirect user to message page
344: if (approvalRequired) {
345: throw new DecisionRequiredException(
346: "This profile must be approved before it becomes active");
347: }
348:
349: // If the profile doesn't need approval, then just log the user in
350:
351: try {
352: AuthenticationManager mgr = m_engine
353: .getAuthenticationManager();
354: if (newProfile && !mgr.isContainerAuthenticated()) {
355: mgr.login(session, profile.getLoginName(), profile
356: .getPassword());
357: }
358: } catch (WikiException e) {
359: throw new WikiSecurityException(e.getMessage());
360: }
361:
362: // Alert all listeners that the profile changed...
363: // ...this will cause credentials to be reloaded in the wiki session
364: fireEvent(WikiSecurityEvent.PROFILE_SAVE, session, profile);
365: }
366:
367: // For existing accounts, just save the profile
368: else {
369: // If login name changed, rename it first
370: if (nameChanged
371: && oldProfile != null
372: && !oldProfile.getLoginName().equals(
373: profile.getLoginName())) {
374: getUserDatabase().rename(oldProfile.getLoginName(),
375: profile.getLoginName());
376: }
377:
378: // Now, save the profile (userdatabase will take care of timestamps for us)
379: getUserDatabase().save(profile);
380:
381: if (nameChanged) {
382: // Fire an event if the login name or full name changed
383: UserProfile[] profiles = new UserProfile[] {
384: oldProfile, profile };
385: fireEvent(WikiSecurityEvent.PROFILE_NAME_CHANGED,
386: session, profiles);
387: } else {
388: // Fire an event that says we have new a new profile (new principals)
389: fireEvent(WikiSecurityEvent.PROFILE_SAVE, session,
390: profile);
391: }
392: }
393: }
394:
395: /**
396: * <p> Extracts user profile parameters from the HTTP request and populates
397: * a UserProfile with them. The UserProfile will either be a copy of the
398: * user's existing profile (if one can be found), or a new profile (if not).
399: * The rules for populating the profile as as follows: </p> <ul> <li>If the
400: * <code>email</code> or <code>password</code> parameter values differ
401: * from those in the existing profile, the passed parameters override the
402: * old values.</li> <li>For new profiles, the user-supplied
403: * <code>fullname</code parameter is always
404: * used; for existing profiles the existing value is used, and whatever
405: * value the user supplied is discarded. The wiki name is automatically
406: * computed by taking the full name and extracting all whitespace.</li>
407: * <li>In all cases, the
408: * created/last modified timestamps of the user's existing or new profile
409: * always override whatever values the user supplied.</li> <li>If
410: * container authentication is used, the login name property of the profile
411: * is set to the name of
412: * {@link com.ecyrd.jspwiki.WikiSession#getLoginPrincipal()}. Otherwise,
413: * the value of the <code>loginname</code> parameter is used.</li> </ul>
414: * @param context the current wiki context
415: * @return a new, populated user profile
416: */
417: public final UserProfile parseProfile(WikiContext context) {
418: // Retrieve the user's profile (may have been previously cached)
419: UserProfile profile = getUserProfile(context.getWikiSession());
420: HttpServletRequest request = context.getHttpRequest();
421:
422: // Extract values from request stream (cleanse whitespace as needed)
423: String loginName = request.getParameter(PARAM_LOGINNAME);
424: String password = request.getParameter(PARAM_PASSWORD);
425: String fullname = request.getParameter(PARAM_FULLNAME);
426: String email = request.getParameter(PARAM_EMAIL);
427: loginName = InputValidator.isBlank(loginName) ? null
428: : loginName;
429: password = InputValidator.isBlank(password) ? null : password;
430: fullname = InputValidator.isBlank(fullname) ? null : fullname;
431: email = InputValidator.isBlank(email) ? null : email;
432:
433: // A special case if we have container authentication
434: if (m_engine.getAuthenticationManager()
435: .isContainerAuthenticated()) {
436: // If authenticated, login name is always taken from container
437: if (context.getWikiSession().isAuthenticated()) {
438: loginName = context.getWikiSession()
439: .getLoginPrincipal().getName();
440: }
441: }
442:
443: // Set the profile fields!
444: profile.setLoginName(loginName);
445: profile.setEmail(email);
446: profile.setFullname(fullname);
447: profile.setPassword(password);
448: return profile;
449: }
450:
451: /**
452: * Validates a user profile, and appends any errors to the session errors
453: * list. If the profile is new, the password will be checked to make sure it
454: * isn't null. Otherwise, the password is checked for length and that it
455: * matches the value of the 'password2' HTTP parameter. Note that we have a
456: * special case when container-managed authentication is used and the user
457: * is not authenticated; this will always cause validation to fail. Any
458: * validation errors are added to the wiki session's messages collection
459: * (see {@link WikiSession#getMessages()}.
460: * @param context the current wiki context
461: * @param profile the supplied UserProfile
462: */
463: public final void validateProfile(WikiContext context,
464: UserProfile profile) {
465: boolean isNew = profile.isNew();
466: WikiSession session = context.getWikiSession();
467: InputValidator validator = new InputValidator(SESSION_MESSAGES,
468: session);
469: ResourceBundle rb = context
470: .getBundle(InternationalizationManager.CORE_BUNDLE);
471:
472: //
473: // Query the SpamFilter first
474: //
475:
476: List ls = m_engine.getFilterManager().getFilterList();
477: for (Iterator i = ls.iterator(); i.hasNext();) {
478: PageFilter pf = (PageFilter) i.next();
479:
480: if (pf instanceof SpamFilter) {
481: if (((SpamFilter) pf).isValidUserProfile(context,
482: profile) == false) {
483: session.addMessage(SESSION_MESSAGES,
484: "Invalid userprofile");
485: return;
486: }
487: break;
488: }
489: }
490:
491: // If container-managed auth and user not logged in, throw an error
492: // unless we're allowed to add profiles to the container
493: if (m_engine.getAuthenticationManager()
494: .isContainerAuthenticated()
495: && !context.getWikiSession().isAuthenticated()
496: && !getUserDatabase().isSharedWithContainer()) {
497: session
498: .addMessage(
499: SESSION_MESSAGES,
500: rb
501: .getString("security.error.createprofilebeforelogin"));
502: }
503:
504: validator.validateNotNull(profile.getLoginName(), rb
505: .getString("security.user.loginname"));
506: validator.validateNotNull(profile.getFullname(), rb
507: .getString("security.user.fullname"));
508: validator
509: .validate(profile.getEmail(), rb
510: .getString("security.user.email"),
511: InputValidator.EMAIL);
512:
513: // If new profile, passwords must match and can't be null
514: if (!m_engine.getAuthenticationManager()
515: .isContainerAuthenticated()) {
516: String password = profile.getPassword();
517: if (password == null) {
518: if (isNew) {
519: session.addMessage(SESSION_MESSAGES, rb
520: .getString("security.error.blankpassword"));
521: }
522: } else {
523: HttpServletRequest request = context.getHttpRequest();
524: String password2 = (request == null) ? null : request
525: .getParameter("password2");
526: if (!password.equals(password2)) {
527: session
528: .addMessage(
529: SESSION_MESSAGES,
530: rb
531: .getString("security.error.passwordnomatch"));
532: }
533: }
534: }
535:
536: UserProfile otherProfile;
537: String fullName = profile.getFullname();
538: String loginName = profile.getLoginName();
539:
540: // It's illegal to use as a full name someone else's login name
541: try {
542: otherProfile = getUserDatabase().find(fullName);
543: if (otherProfile != null && !profile.equals(otherProfile)
544: && !fullName.equals(otherProfile.getFullname())) {
545: Object[] args = { fullName };
546: session
547: .addMessage(
548: SESSION_MESSAGES,
549: MessageFormat
550: .format(
551: rb
552: .getString("security.error.illegalfullname"),
553: args));
554: }
555: } catch (NoSuchPrincipalException e) { /* It's clean */
556: }
557:
558: // It's illegal to use as a login name someone else's full name
559: try {
560: otherProfile = getUserDatabase().find(loginName);
561: if (otherProfile != null && !profile.equals(otherProfile)
562: && !loginName.equals(otherProfile.getLoginName())) {
563: Object[] args = { loginName };
564: session
565: .addMessage(
566: SESSION_MESSAGES,
567: MessageFormat
568: .format(
569: rb
570: .getString("security.error.illegalloginname"),
571: args));
572: }
573: } catch (NoSuchPrincipalException e) { /* It's clean */
574: }
575: }
576:
577: public Principal[] listWikiNames() throws WikiSecurityException {
578: return getUserDatabase().getWikiNames();
579: }
580:
581: /**
582: * This is a database that gets used if nothing else is available. It does
583: * nothing of note - it just mostly thorws NoSuchPrincipalExceptions if
584: * someone tries to log in.
585: * @author Janne Jalkanen
586: */
587: public static class DummyUserDatabase extends AbstractUserDatabase {
588:
589: /**
590: * No-op.
591: * @throws WikiSecurityException never...
592: */
593: public void commit() throws WikiSecurityException {
594: // No operation
595: }
596:
597: /**
598: * No-op.
599: * @param loginName the login name to delete
600: * @throws WikiSecurityException never...
601: */
602: public void deleteByLoginName(String loginName)
603: throws WikiSecurityException {
604: // No operation
605: }
606:
607: /**
608: * No-op; always throws <code>NoSuchPrincipalException</code>.
609: * @param index the name to search for
610: * @return the user profile
611: * @throws NoSuchPrincipalException never...
612: */
613: public UserProfile findByEmail(String index)
614: throws NoSuchPrincipalException {
615: throw new NoSuchPrincipalException(
616: "No user profiles available");
617: }
618:
619: /**
620: * No-op; always throws <code>NoSuchPrincipalException</code>.
621: * @param index the name to search for
622: * @return the user profile
623: * @throws NoSuchPrincipalException never...
624: */
625: public UserProfile findByFullName(String index)
626: throws NoSuchPrincipalException {
627: throw new NoSuchPrincipalException(
628: "No user profiles available");
629: }
630:
631: /**
632: * No-op; always throws <code>NoSuchPrincipalException</code>.
633: * @param index the name to search for
634: * @return the user profile
635: * @throws NoSuchPrincipalException never...
636: */
637: public UserProfile findByLoginName(String index)
638: throws NoSuchPrincipalException {
639: throw new NoSuchPrincipalException(
640: "No user profiles available");
641: }
642:
643: /**
644: * No-op; always throws <code>NoSuchPrincipalException</code>.
645: * @param index the name to search for
646: * @return the user profile
647: * @throws NoSuchPrincipalException never...
648: */
649: public UserProfile findByWikiName(String index)
650: throws NoSuchPrincipalException {
651: throw new NoSuchPrincipalException(
652: "No user profiles available");
653: }
654:
655: /**
656: * No-op.
657: * @return a zero-length array
658: * @throws WikiSecurityException never...
659: */
660: public Principal[] getWikiNames() throws WikiSecurityException {
661: return new Principal[0];
662: }
663:
664: /**
665: * No-op.
666: * @param engine the wiki engine
667: * @param props the properties used to initialize the wiki engine
668: * @throws NoRequiredPropertyException never...
669: */
670: public void initialize(WikiEngine engine, Properties props)
671: throws NoRequiredPropertyException {
672: }
673:
674: /**
675: * No-op.
676: * @return <code>false</code>
677: */
678: public boolean isSharedWithContainer() {
679: return false;
680: }
681:
682: /**
683: * No-op; always throws <code>NoSuchPrincipalException</code>.
684: * @param loginName the login name
685: * @param newName the proposed new login name
686: * @throws DuplicateUserException never...
687: * @throws WikiSecurityException never...
688: */
689: public void rename(String loginName, String newName)
690: throws DuplicateUserException, WikiSecurityException {
691: throw new NoSuchPrincipalException(
692: "No user profiles available");
693: }
694:
695: /**
696: * No-op.
697: * @param profile the user profile
698: * @throws WikiSecurityException never...
699: */
700: public void save(UserProfile profile)
701: throws WikiSecurityException {
702: }
703:
704: }
705:
706: // workflow task inner classes....................................................
707:
708: /**
709: * Inner class that handles the actual profile save action. Instances
710: * of this class are assumed to have been added to an approval workflow via
711: * {@link com.ecyrd.jspwiki.workflow.WorkflowBuilder#buildApprovalWorkflow(Principal, String, Task, String, com.ecyrd.jspwiki.workflow.Fact[], Task, String)};
712: * they will not function correctly otherwise.
713: *
714: * @author Andrew Jaquith
715: */
716: public static class SaveUserProfileTask extends Task {
717: private final UserDatabase m_db;
718: private final WikiEngine m_engine;
719:
720: /**
721: * Constructs a new Task for saving a user profile.
722: * @param engine the wiki engine
723: */
724: public SaveUserProfileTask(WikiEngine engine) {
725: super (SAVE_TASK_MESSAGE_KEY);
726: m_engine = engine;
727: m_db = engine.getUserManager().getUserDatabase();
728: }
729:
730: /**
731: * Saves the user profile to the user database.
732: * @return {@link com.ecyrd.jspwiki.workflow.Outcome#STEP_COMPLETE} if the
733: * task completed successfully
734: * @throws WikiException if the save did not complete for some reason
735: */
736: public Outcome execute() throws WikiException {
737: // Retrieve user profile
738: UserProfile profile = (UserProfile) getWorkflow()
739: .getAttribute(SAVED_PROFILE);
740:
741: // Save the profile (userdatabase will take care of timestamps for us)
742: m_db.save(profile);
743:
744: // Send e-mail if user supplied an e-mail address
745: if (profile.getEmail() != null) {
746: try {
747: String app = m_engine.getApplicationName();
748: String to = profile.getEmail();
749: String subject = "Welcome to " + app;
750: String content = "Congratulations! Your new profile on "
751: + app
752: + " has been created. Your profile details are as follows: \n\n"
753: + "Login name: "
754: + profile.getLoginName()
755: + "\n"
756: + "Your name : "
757: + profile.getFullname()
758: + "\n"
759: + "E-mail : "
760: + profile.getEmail()
761: + "\n\n"
762: + "If you forget your password, you can reset it at "
763: + m_engine.getURL(WikiContext.LOGIN, null,
764: null, true);
765: MailUtil
766: .sendMessage(m_engine, to, subject, content);
767: } catch (AddressException e) {
768: } catch (MessagingException e) {
769: log
770: .error("Could not send registration confirmation e-mail. Is the e-mail server running?");
771: }
772: }
773:
774: return Outcome.STEP_COMPLETE;
775: }
776: }
777:
778: // events processing .......................................................
779:
780: /**
781: * Registers a WikiEventListener with this instance.
782: * This is a convenience method.
783: * @param listener the event listener
784: */
785: public final synchronized void addWikiEventListener(
786: WikiEventListener listener) {
787: WikiEventManager.addWikiEventListener(this , listener);
788: }
789:
790: /**
791: * Un-registers a WikiEventListener with this instance.
792: * This is a convenience method.
793: * @param listener the event listener
794: */
795: public final synchronized void removeWikiEventListener(
796: WikiEventListener listener) {
797: WikiEventManager.removeWikiEventListener(this , listener);
798: }
799:
800: /**
801: * Fires a WikiSecurityEvent of the provided type, Principal and target Object
802: * to all registered listeners.
803: *
804: * @see com.ecyrd.jspwiki.event.WikiSecurityEvent
805: * @param type the event type to be fired
806: * @param session the wiki session supporting the event
807: * @param profile the user profile (or array of user profiles), which may be <code>null</code>
808: */
809: protected final void fireEvent(int type, WikiSession session,
810: Object profile) {
811: if (WikiEventManager.isListening(this )) {
812: WikiEventManager.fireEvent(this , new WikiSecurityEvent(
813: session, type, profile));
814: }
815: }
816:
817: /**
818: * Implements the JSON API for usermanager.
819: *
820: * @author Janne Jalkanen
821: */
822: public final class JSONUserModule implements RPCCallable {
823: /**
824: * Directly returns the UserProfile object attached to an uid.
825: *
826: * @param uid The user id (e.g. WikiName)
827: * @return A UserProfile object
828: * @throws NoSuchPrincipalException If such a name does not exist.
829: */
830: public UserProfile getUserInfo(String uid)
831: throws NoSuchPrincipalException {
832: log.info("request " + uid);
833: UserProfile prof = getUserDatabase().find(uid);
834:
835: log.info("answer " + prof);
836:
837: return prof;
838: }
839: }
840: }
|