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