/*
* JBoss, Home of Professional Open Source
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.jboss.seam.wiki.core.action;
import org.jboss.seam.Component;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Factory;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Logger;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.datamodel.DataModel;
import org.jboss.seam.annotations.security.Restrict;
import org.jboss.seam.contexts.Contexts;
import org.jboss.seam.faces.Renderer;
import org.jboss.seam.framework.EntityHome;
import org.jboss.seam.international.StatusMessages;
import org.jboss.seam.log.Log;
import org.jboss.seam.security.AuthorizationException;
import org.jboss.seam.security.Identity;
import org.jboss.seam.wiki.core.action.prefs.UserManagementPreferences;
import org.jboss.seam.wiki.core.action.prefs.WikiPreferences;
import org.jboss.seam.wiki.core.dao.BlacklistDAO;
import org.jboss.seam.wiki.core.dao.UserDAO;
import org.jboss.seam.wiki.core.dao.WikiNodeDAO;
import org.jboss.seam.wiki.core.model.Blacklist;
import org.jboss.seam.wiki.core.model.Role;
import org.jboss.seam.wiki.core.model.User;
import org.jboss.seam.wiki.core.model.WikiComment;
import org.jboss.seam.wiki.core.model.WikiDocument;
import org.jboss.seam.wiki.core.model.WikiNode;
import org.jboss.seam.wiki.core.model.WikiUploadImage;
import org.jboss.seam.wiki.core.upload.Uploader;
import org.jboss.seam.wiki.core.exception.InvalidWikiRequestException;
import org.jboss.seam.wiki.core.wikitext.editor.WikiTextEditor;
import org.jboss.seam.wiki.preferences.PreferenceVisibility;
import org.jboss.seam.wiki.preferences.Preferences;
import org.jboss.seam.wiki.preferences.PreferenceProvider;
import org.jboss.seam.wiki.preferences.metamodel.PreferenceEntity;
import org.jboss.seam.wiki.util.Hash;
import org.jboss.seam.wiki.util.WikiUtil;
import static org.jboss.seam.international.StatusMessage.Severity.WARN;
import static org.jboss.seam.international.StatusMessage.Severity.INFO;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.faces.context.FacesContext;
@Name("userHome")
@Scope(ScopeType.CONVERSATION)
public class UserHome extends EntityHome<User> {
@Logger
static Log log;
// TODO: This is a performance optimization, our EM is always already joined (SMPC)
//protected void joinTransaction() {}
@In
private StatusMessages statusMessages;
@In(required = false)
private String clientAddress;
@In
private UserDAO userDAO;
@In
private BlacklistDAO blacklistDAO;
@In
private Hash hashUtil;
@In(create = true)
private Renderer renderer;
@In("#{preferences.get('UserManagement')}")
UserManagementPreferences prefs;
private String oldUsername;
private String password;
private String passwordControl;
private List<Role> roles;
private org.jboss.seam.wiki.core.model.Role defaultRole;
private Uploader uploader;
private Long createdWikiNodeCount;
private String requestedUsername;
private WikiTextEditor bioTextEditor;
private WikiTextEditor signatureTextEditor;
public Uploader getUploader() {
return uploader;
}
public void setUploader(Uploader uploader) {
this.uploader = uploader;
}
public Long getUserId() {
return (Long)getId();
}
public void setUserId(Long userId) {
setId(userId);
}
public String getRequestedUsername() {
return requestedUsername;
}
public void setRequestedUsername(String requestedUsername) {
getLog().debug("requested user name: " + requestedUsername);
this.requestedUsername = requestedUsername;
}
public void init() {
if (isManaged()) {
if (!Identity.instance().hasPermission("User", "edit", getInstance()) ) {
throw new AuthorizationException("You don't have permission for this operation");
}
if (roles == null) roles = getInstance().getRoles();
if (oldUsername == null) oldUsername = getInstance().getUsername();
createdWikiNodeCount = userDAO.countNodesCreatedBy(getInstance().getId());
uploader = (Uploader)Component.getInstance(Uploader.class);
} else {
if (!prefs.getEnableRegistration() &&
!Identity.instance().hasPermission("User", "isAdmin", Component.getInstance("currentUser"))) {
throw new AuthorizationException("User registration is disabled");
}
if (defaultRole == null) defaultRole = (Role)Component.getInstance("newUserDefaultRole");
}
if (bioTextEditor == null || signatureTextEditor == null) {
bioTextEditor = new WikiTextEditor("bio", 1023, false, false, 5);
signatureTextEditor = new WikiTextEditor("signature", 1023, false, false, 5);
syncInstanceToWikiTextEditors();
}
}
public void initEdit() {
if (getUserId() == null) {
throw new InvalidWikiRequestException("Missing userId request parameter");
}
init();
}
public void initDisplay() {
if (getUserId() == null && getRequestedUsername() == null) {
throw new InvalidWikiRequestException("Missing userId or username request parameter");
}
}
@Override
protected void initInstance() {
if ( isIdDefined() || (getRequestedUsername() != null && getRequestedUsername().length() >0) ) {
if ( !isTransactionMarkedRollback() ) {
setInstance( find() );
}
} else {
setInstance( createInstance() );
}
}
@Override
protected User loadInstance() {
if (getRequestedUsername() != null && getRequestedUsername().length() >0) {
getLog().debug("loading user from database: " + getRequestedUsername());
return userDAO.findUser(getRequestedUsername(), false, true);
} else {
return getEntityManager().find(getEntityClass(), getId());
}
}
@Override
public String persist() {
if (clientAddress != null)
{
getInstance().setRegisteredAddress(clientAddress);
}
if (isBlacklisted())
{
return "blacklisted";
}
// Validate
if (!isUniqueUsername() ||
!passwordAndControlNotNull() ||
!passwordMatchesRegex() ||
!passwordMatchesControl()) {
// Force re-entry
setPassword(null);
setPasswordControl(null);
return null;
}
// Assign default role
getInstance().getRoles().add(defaultRole);
// Set password hash
getInstance().setPasswordHash(hashUtil.hash(getPassword()));
if (Identity.instance().hasPermission("User", "isAdmin", Component.getInstance("currentUser"))) {
// Current user is admin and creating a new account, the new account is active automatically
getInstance().setActivated(true);
String outcome = super.persist();
if (outcome != null) {
org.jboss.seam.core.Events.instance().raiseEvent("User.persisted", getInstance());
}
return outcome;
} else {
// Set activation code (unique user in time)
String seed = getInstance().getUsername() + System.currentTimeMillis() + prefs.getActivationCodeSalt();
getInstance().setActivationCode( ((Hash)Component.getInstance(Hash.class)).hash(seed) );
getLog().debug("setting activation code of newly registered user: " + getInstance().getActivationCode());
String outcome = super.persist();
if (outcome != null) {
getLog().debug("sending activation e-mail to registered user");
// Send confirmation email
renderer.render("/themes/"
+ Preferences.instance().get(WikiPreferences.class).getThemeName()
+ "/mailtemplates/confirmationRegistration.xhtml");
/* For debugging
statusMessages.addFromResourceBundleOrDefault(
INFO,
getMessageKeyPrefix() + "confirmationEmailSent",
"Activiate account: /confirmRegistration.seam?activationCode=" + getInstance().getActivationCode());
*/
org.jboss.seam.core.Events.instance().raiseEvent("User.registered", getInstance());
org.jboss.seam.core.Events.instance().raiseEvent("User.persisted", getInstance());
}
return outcome;
}
}
@Override
@Restrict("#{s:hasPermission('User', 'edit', userHome.instance)}")
public String update() {
if (isManaged() && getCreatedWikiNodeCount() != null && getCreatedWikiNodeCount() > 0) {
if (!validateWikiTextEditors()) {
return null;
}
syncWikiTextEditorsToInstance();
}
if (uploader.hasData()) {
uploader.uploadNewInstance();
if (WikiUploadImage.class.isAssignableFrom(uploader.getUpload().getClass())) {
WikiUploadImage portrait = (WikiUploadImage)uploader.getUpload();
getLog().debug("updating portrait file data/type");
getInstance().getProfile().setImageContentType(portrait.getContentType());
getInstance().getProfile().setImage(
WikiUtil.resizeImage(portrait.getData(), portrait.getContentType(), 80) // TODO: Make size configurable?
);
getInstance().getProfile().setSmallImage(
WikiUtil.resizeImage(portrait.getData(), portrait.getContentType(), 40) // TODO: Make size configurable?
);
} else {
statusMessages.addFromResourceBundleOrDefault(
WARN,
"lacewiki.msg.userHome.WrongPortraitImageType",
"The file type '{0}' is not supported, the portrait was not updated.",
uploader.getUpload().getContentType()
);
}
}
uploader.reset();
User adminUser = (User)Component.getInstance("adminUser");
User guestUser = (User)Component.getInstance("guestUser");
if ( !getInstance().getId().equals(adminUser.getId()) &&
!getInstance().getId().equals(guestUser.getId()) &&
roles != null && roles.size() > 0) {
// Roles
getInstance().setRoles(new ArrayList<Role>()); // Clear out the collection
getInstance().getRoles().addAll(roles);
}
// Preferences
if (preferenceEditor != null) {
String editorFailed = preferenceEditor.save();
if (editorFailed != null) return null;
}
boolean loginCredentialsModified = false;
// User wants to change his password
if (getPassword() != null && getPassword().length() != 0) {
if (!passwordAndControlNotNull() ||
!passwordMatchesRegex() ||
!passwordMatchesControl()) {
// Force re-entry
setPassword(null);
setPasswordControl(null);
return null;
} else {
// Set password hash
getInstance().setPasswordHash(hashUtil.hash(getPassword()));
loginCredentialsModified = true;
}
}
// User changed his username
if (!getInstance().getUsername().equals(oldUsername)) {
loginCredentialsModified = true;
// Validate
if (!isUniqueUsername()) return null;
}
if (Identity.instance().hasPermission("User", "isAdmin", Component.getInstance("currentUser"))) {
// Current user is admin and activated an account
if (getInstance().isActivated()) {
getInstance().setActivationCode(null);
}
}
String outcome = super.update();
if (outcome != null) {
org.jboss.seam.core.Events.instance().raiseEvent("User.updated", getInstance());
User currentUser = (User)Component.getInstance("currentUser");
if (getInstance().getId().equals(currentUser.getId())) {
// Updated profile of currently logged-in user
Contexts.getSessionContext().set("currentUser", getInstance());
// TODO: If identity.logout() wouldn't kill my session, I could call it here...
// And I don't have cleartext password in all cases, so I can't relogin the user automatically
if (loginCredentialsModified) {
Identity.instance().logout();
return "updatedCurrentCredentials";
}
}
}
return outcome;
}
@Override
@Restrict("#{s:hasPermission('User', 'delete', userHome.instance)}")
public String remove() {
// All nodes created by this user are reset to be created by the admin user
userDAO.resetNodeCreatorToAdmin(getInstance());
// Remove preferences for this user
PreferenceProvider prefProvider = (PreferenceProvider)Component.getInstance("preferenceProvider");
prefProvider.deleteUserPreferenceValues(getInstance());
prefProvider.flush();
String outcome = super.remove();
if (outcome != null) {
org.jboss.seam.core.Events.instance().raiseEvent("User.removed", getInstance());
}
return outcome;
}
@Restrict("#{s:hasPermission('User', 'delete', userHome.instance)}")
public String nuke() {
// First delete their comments
DocumentHome documentHome = (DocumentHome) Component.getInstance("documentHome");
WikiNodeDAO wikiNodeDAO = (WikiNodeDAO) Component.getInstance("wikiNodeDAO");
// Find all the content that this user has created
List<WikiNode> userNodes = wikiNodeDAO.findWikiNodes(getInstance());
Set<WikiNode> nodesToDelete = new HashSet<WikiNode>();
// Build a list of all the child nodes of the user's nodes
for (WikiNode node : userNodes)
{
recursiveAddChildren(wikiNodeDAO, node, nodesToDelete);
}
while (!nodesToDelete.isEmpty())
{
WikiNode nodeToDelete = null;
main: for (WikiNode node : nodesToDelete)
{
// We need to find a node without children contained in the same set (which should contain any children if they exist)
for (WikiNode n : nodesToDelete)
{
if (n.getParent().equals(node)) continue main;
}
nodeToDelete = node;
break;
}
if (nodeToDelete == null)
{
throw new IllegalStateException("Error while deleting child nodes - no childless node found in set.");
}
else
{
if (nodeToDelete instanceof WikiComment)
{
WikiComment comment = (WikiComment) nodeToDelete;
documentHome.setId(getCommentDocument(comment).getId());
CommentHome commentHome = (CommentHome) Component.getInstance("commentHome");
commentHome.setId(comment.getId());
commentHome.remove(comment.getId());
}
else if (nodeToDelete instanceof WikiDocument)
{
documentHome.setId(nodeToDelete.getId());
documentHome.reallyRemove();
}
else
{
log.info("Unhandled node found, could not delete: " + nodeToDelete);
}
}
nodesToDelete.remove(nodeToDelete);
}
Blacklist blacklist = new Blacklist();
blacklist.setEmail(getInstance().getEmail());
blacklist.setIpAddress(getInstance().getRegisteredAddress());
getEntityManager().persist(blacklist);
// Remove preferences for this user
PreferenceProvider prefProvider = (PreferenceProvider)Component.getInstance("preferenceProvider");
prefProvider.deleteUserPreferenceValues(getInstance());
prefProvider.flush();
String outcome = super.remove();
if (outcome != null) {
org.jboss.seam.core.Events.instance().raiseEvent("User.removed", getInstance());
}
return outcome;
}
private void recursiveAddChildren(WikiNodeDAO wikiNodeDAO, WikiNode parent, Set<WikiNode> nodeSet)
{
if (nodeSet.contains(parent)) return;
List<WikiNode> children = wikiNodeDAO.findChildren(parent, WikiNode.SortableProperty.createdOn, true, 0, 0);
for (WikiNode child : children)
{
recursiveAddChildren(wikiNodeDAO, child, nodeSet);
}
nodeSet.add(parent);
}
private WikiDocument getCommentDocument(WikiComment comment)
{
WikiNode parent = comment.getParent();
while (!(parent instanceof WikiDocument))
{
parent = parent.getParent();
}
return (WikiDocument) parent;
}
@Restrict("#{s:hasPermission('User', 'edit', userHome.instance)}")
public void removePortrait() {
getInstance().getProfile().setImage(null);
getInstance().getProfile().setImageContentType(null);
statusMessages.addFromResourceBundleOrDefault(
INFO,
"lacewiki.msg.userHome.PortraitRemoved",
"The portrait has been removed, save to make changes permanent."
);
}
protected void syncInstanceToWikiTextEditors() {
bioTextEditor.setValue(getInstance().getProfile().getBio());
signatureTextEditor.setValue(getInstance().getProfile().getSignature());
}
protected void syncWikiTextEditorsToInstance() {
getInstance().getProfile().setBio(bioTextEditor.getValue());
getInstance().getProfile().setSignature(signatureTextEditor.getValue());
}
protected boolean validateWikiTextEditors() {
bioTextEditor.validate();
signatureTextEditor.validate();
return bioTextEditor.isValid() && signatureTextEditor.isValid();
}
/* -------------------------- Messages ------------------------------ */
@Override
protected void createdMessage() {
statusMessages.addFromResourceBundleOrDefault(
INFO,
"lacewiki.msg.User.Persist",
"User account '{0}' has been saved.",
getInstance().getUsername()
);
}
@Override
protected void updatedMessage() {
statusMessages.addFromResourceBundleOrDefault(
INFO,
"lacewiki.msg.User.Update",
"User account '{0}' has been updated.",
getInstance().getUsername()
);
}
@Override
protected void deletedMessage() {
statusMessages.addFromResourceBundleOrDefault(
INFO,
"lacewiki.msg.User.Delete",
"User account '{0}' has been deleted.",
getInstance().getUsername()
);
}
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getPasswordControl() { return passwordControl; }
public void setPasswordControl(String passwordControl) { this.passwordControl = passwordControl; }
public List<Role> getRoles() { return roles; }
@Restrict("#{s:hasPermission('User', 'editRoles', currentUser)}")
public void setRoles(List<Role> roles) { this.roles = roles; }
@Restrict("#{s:hasPermission('User', 'isAdmin', currentUser)}")
public void createHomeDirectory() {
Authenticator auth = (Authenticator)Component.getInstance(Authenticator.class);
auth.createHomeDirectory(getInstance());
statusMessages.addFromResourceBundleOrDefault(
INFO,
"lacewiki.msg.HomeDirectoryCreated",
"New home directory has been queued, save settings to commit."
);
}
// Validation rules for persist(), update(), and remove();
public boolean passwordAndControlNotNull() {
if (getPassword() == null || getPassword().length() == 0 ||
getPasswordControl() == null || getPasswordControl().length() == 0) {
statusMessages.addToControlFromResourceBundleOrDefault(
"passwordControl",
WARN,
"lacewiki.msg.PasswordOrPasswordControlEmpty",
"Please enter your password twice!"
);
return false;
}
return true;
}
public boolean passwordMatchesRegex() {
Matcher matcher = Pattern.compile(prefs.getPasswordRegex()).matcher(getPassword());
if (!matcher.find()) {
statusMessages.addToControlFromResourceBundleOrDefault(
"password",
WARN,
"lacewiki.msg.PasswordDoesntMatchPattern",
"Password does not match the pattern: {0}",
prefs.getPasswordRegex()
);
return false;
}
return true;
}
public boolean passwordMatchesControl() {
if (password == null || passwordControl == null || !password.equals(passwordControl) ) {
statusMessages.addToControlFromResourceBundleOrDefault(
"passwordControl",
WARN,
"lacewiki.msg.PasswordControlNoMatch",
"The passwords don't match."
);
return false;
}
return true;
}
public boolean isUniqueUsername() {
User foundUser = userDAO.findUser(getInstance().getUsername(), false, false);
if ( foundUser != null && foundUser != getInstance() ) {
statusMessages.addToControlFromResourceBundleOrDefault(
"username",
WARN,
"lacewiki.msg.UsernameExists",
"A user with that name already exists."
);
return false;
}
return true;
}
public boolean isBlacklisted()
{
return blacklistDAO.isEmailBlacklisted(getInstance().getEmail()) ||
blacklistDAO.isIpAddressBlacklisted(getInstance().getRegisteredAddress());
}
public void validateUsername() {
isUniqueUsername();
}
public void validatePassword() {
if (getPassword() != null && getPassword().length() > 0)
passwordMatchesRegex();
}
public void validatePasswordControl() {
passwordMatchesControl();
}
public long getRatingPoints() {
return userDAO.findRatingPoints(getInstance().getId());
}
public Long getCreatedWikiNodeCount() {
return createdWikiNodeCount;
}
public WikiTextEditor getBioTextEditor() {
return bioTextEditor;
}
public WikiTextEditor getSignatureTextEditor() {
return signatureTextEditor;
}
// ####################### PREFERENCES ##################################
PreferenceEditor preferenceEditor;
@DataModel(value = "userPreferenceEntities")
private List<PreferenceEntity> userPreferenceEntities;
@Factory("userPreferenceEntities")
public void initPreferencesEditor() {
preferenceEditor = (PreferenceEditor)Component.getInstance(PreferenceEditor.class);
preferenceEditor.setVisibilities(new PreferenceVisibility[] {PreferenceVisibility.USER});
preferenceEditor.setUser(getInstance());
userPreferenceEntities = preferenceEditor.getPreferenceEntities();
Contexts.getConversationContext().set("preferenceEditor", preferenceEditor);
}
}