You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openmeetings.apache.org by so...@apache.org on 2018/04/21 15:54:25 UTC

[openmeetings] branch master updated: [OPENMEETINGS-1864] purge seems to work

This is an automated email from the ASF dual-hosted git repository.

solomax pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/openmeetings.git


The following commit(s) were added to refs/heads/master by this push:
     new 1bf8e34  [OPENMEETINGS-1864] purge seems to work
1bf8e34 is described below

commit 1bf8e34cba056555b37397b3f44f67e3ca67b2a9
Author: Maxim Solodovnik <so...@gmail.com>
AuthorDate: Sat Apr 21 22:47:26 2018 +0700

    [OPENMEETINGS-1864] purge seems to work
---
 .../apache/openmeetings/db/dao/user/UserDao.java   | 11 +++++
 .../openmeetings/service/user/UserManager.java     | 11 ++---
 .../org/apache/openmeetings/util/crypt/ICrypt.java |  2 +
 .../util/crypt/SCryptImplementation.java           | 37 ++++++++++------
 ...{AdminSavePanel.html => AdminActionsPanel.html} |  5 +--
 ...{AdminSavePanel.java => AdminActionsPanel.java} | 37 +++-------------
 .../openmeetings/web/admin/AdminBaseForm.java      |  4 +-
 .../openmeetings/web/admin/users/UserForm.java     | 49 ++++++++++-----------
 .../web/common/ConfirmableAjaxBorder.java          | 51 ++++++++++++++++------
 ...SaveRefreshPanel.html => FormActionsPanel.html} |  7 ++-
 ...SaveRefreshPanel.java => FormActionsPanel.java} | 40 +++++++++++++----
 .../openmeetings/web/user/profile/ProfileForm.java | 21 ++++++++-
 12 files changed, 168 insertions(+), 107 deletions(-)

diff --git a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/user/UserDao.java b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/user/UserDao.java
index b0221c0..060f310 100644
--- a/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/user/UserDao.java
+++ b/openmeetings-db/src/main/java/org/apache/openmeetings/db/dao/user/UserDao.java
@@ -340,6 +340,8 @@ public class UserDao implements IGroupAdminDataProviderDao<User> {
 					.setParameter("email", String.format("%%%s%%", u.getAddress().getEmail()))
 					.executeUpdate();
 			}
+			u.setActivatehash(null);
+			u.setResethash(null);
 			u.setDeleted(true);
 			u.setSipUser(new AsteriskSipUser());
 			u.setAddress(new Address());
@@ -349,8 +351,17 @@ public class UserDao implements IGroupAdminDataProviderDao<User> {
 			u.setFirstname(purged);
 			u.setLastname(purged);
 			u.setLogin(purged);
+			u.setGroupUsers(new ArrayList<>());
+			u.setRights(new HashSet<>());
+			u.setTimeZoneId(null);
 			File pic = OmFileHelper.getUserProfilePicture(u.getId(), u.getPictureuri(), null);
 			u.setPictureuri(null);
+			ICrypt crypt = CryptProvider.get();
+			try {
+				u.updatePassword(crypt.randomPassword(25));
+			} catch (NoSuchAlgorithmException e) {
+				log.error("Unexpected exception while updating password");
+			}
 			update(u, userId);
 			// this should be last action, so file will be deleted in case there were no errors
 			if (pic != null) {
diff --git a/openmeetings-service/src/main/java/org/apache/openmeetings/service/user/UserManager.java b/openmeetings-service/src/main/java/org/apache/openmeetings/service/user/UserManager.java
index 9117c1f..2dc4f9f 100644
--- a/openmeetings-service/src/main/java/org/apache/openmeetings/service/user/UserManager.java
+++ b/openmeetings-service/src/main/java/org/apache/openmeetings/service/user/UserManager.java
@@ -30,12 +30,10 @@ import static org.apache.openmeetings.util.OpenmeetingsVariables.getMinLoginLeng
 
 import java.io.IOException;
 import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
 import java.util.Date;
 import java.util.Locale;
 import java.util.UUID;
 
-import org.apache.commons.codec.binary.Base64;
 import org.apache.openmeetings.db.dao.basic.ConfigurationDao;
 import org.apache.openmeetings.db.dao.label.LabelDao;
 import org.apache.openmeetings.db.dao.user.GroupDao;
@@ -48,6 +46,8 @@ import org.apache.openmeetings.db.entity.user.User.Right;
 import org.apache.openmeetings.db.entity.user.User.Type;
 import org.apache.openmeetings.service.mail.EmailManager;
 import org.apache.openmeetings.util.OmException;
+import org.apache.openmeetings.util.crypt.CryptProvider;
+import org.apache.openmeetings.util.crypt.ICrypt;
 import org.apache.wicket.util.string.Strings;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -262,10 +262,6 @@ public class UserManager implements IUserManager {
 			return null;
 		}
 		// generate random password
-		SecureRandom rnd = new SecureRandom();
-		byte[] rawPass = new byte[25];
-		rnd.nextBytes(rawPass);
-		String pass = Base64.encodeBase64String(rawPass);
 		// check if the user already exists and register new one if it's needed
 		if (u == null) {
 			u = getNewUserInstance(null);
@@ -292,7 +288,8 @@ public class UserManager implements IUserManager {
 			}
 		}
 		u.setLastlogin(new Date());
-		u = userDao.update(u, pass, Long.valueOf(-1));
+		ICrypt crypt = CryptProvider.get();
+		u = userDao.update(u, crypt.randomPassword(25), Long.valueOf(-1));
 
 		return u;
 	}
diff --git a/openmeetings-util/src/main/java/org/apache/openmeetings/util/crypt/ICrypt.java b/openmeetings-util/src/main/java/org/apache/openmeetings/util/crypt/ICrypt.java
index 88b8c7f..dcb6f12 100644
--- a/openmeetings-util/src/main/java/org/apache/openmeetings/util/crypt/ICrypt.java
+++ b/openmeetings-util/src/main/java/org/apache/openmeetings/util/crypt/ICrypt.java
@@ -58,4 +58,6 @@ public interface ICrypt {
 	 * @return <code>true</code> in case string matches hash, <code>false</code> otherwise
 	 */
 	boolean fallback(String str, String hash);
+
+	String randomPassword(int length);
 }
diff --git a/openmeetings-util/src/main/java/org/apache/openmeetings/util/crypt/SCryptImplementation.java b/openmeetings-util/src/main/java/org/apache/openmeetings/util/crypt/SCryptImplementation.java
index 1fac3b1..c30bfa5 100644
--- a/openmeetings-util/src/main/java/org/apache/openmeetings/util/crypt/SCryptImplementation.java
+++ b/openmeetings-util/src/main/java/org/apache/openmeetings/util/crypt/SCryptImplementation.java
@@ -30,15 +30,27 @@ import org.slf4j.LoggerFactory;
 
 public class SCryptImplementation implements ICrypt {
 	private static final Logger log = LoggerFactory.getLogger(SCryptImplementation.class);
+	private static final ThreadLocal<SecureRandom> rnd = new ThreadLocal<SecureRandom>() {
+		@Override
+		protected SecureRandom initialValue() {
+			SecureRandom sr;
+			try {
+				sr = SecureRandom.getInstance(SECURE_RND_ALG);
+			} catch (NoSuchAlgorithmException e) {
+				log.error("Failed to get instance of SecureRandom {}", SECURE_RND_ALG);
+				sr = new SecureRandom();
+			}
+			return sr;
+		}
+	};
 	private static final String SECURE_RND_ALG = "SHA1PRNG";
 	private static final int COST = 1024 * 16;
 	private static final int KEY_LENGTH = 512;
 	private static final int SALT_LENGTH = 200;
 
-	private static byte[] getSalt() throws NoSuchAlgorithmException {
-		SecureRandom sr = SecureRandom.getInstance(SECURE_RND_ALG);
-		byte[] salt = new byte[SALT_LENGTH];
-		sr.nextBytes(salt);
+	private static byte[] getSalt(int length) {
+		byte[] salt = new byte[length];
+		rnd.get().nextBytes(salt);
 		return salt;
 	}
 
@@ -52,15 +64,9 @@ public class SCryptImplementation implements ICrypt {
 		if (str == null) {
 			return null;
 		}
-		String hash = null;
-		try {
-			byte[] salt = getSalt();
-			String h = hash(str, salt);
-			hash = String.format("%s:%s", h, Base64.encodeBase64String(salt));
-		} catch (NoSuchAlgorithmException e) {
-			log.error("Error", e);
-		}
-		return hash;
+		byte[] salt = getSalt(SALT_LENGTH);
+		String h = hash(str, salt);
+		return String.format("%s:%s", h, Base64.encodeBase64String(salt));
 	}
 
 	@Override
@@ -95,4 +101,9 @@ public class SCryptImplementation implements ICrypt {
 		}
 		return false;
 	}
+
+	@Override
+	public String randomPassword(int length) {
+		return Base64.encodeBase64String(getSalt(length));
+	}
 }
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/AdminSavePanel.html b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/AdminActionsPanel.html
similarity index 86%
rename from openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/AdminSavePanel.html
rename to openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/AdminActionsPanel.html
index a930582..635519c 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/AdminSavePanel.html
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/AdminActionsPanel.html
@@ -26,13 +26,10 @@
 		<div class="ui-button ui-widget ui-corner-all ui-button-icon-only ui-state-highlight" wicket:id="btn-delete" wicket:message="title:157">
 			<span class="ui-button-icon ui-icon ui-icon-closethick"></span>&nbsp;
 		</div>
-		<div class="ui-button ui-widget ui-corner-all ui-button-icon-only ui-state-error" wicket:id="btn-purge" wicket:message="title:admin.purge">
-			<span class="ui-button-icon ui-icon ui-icon-trash"></span>&nbsp;
-		</div>
 		<div class="ui-button ui-widget ui-corner-all ui-button-icon-only" wicket:id="btn-restore" wicket:message="title:admin.restore">
 			<span class="ui-button-icon ui-icon ui-icon-circle-check"></span>&nbsp;
 		</div>
 		<span wicket:id="newRecord"></span>
-		<form wicket:id="form"></form>
+		<form wicket:id="form" style="display: inline;"></form>
 	</wicket:extend>
 </html>
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/AdminSavePanel.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/AdminActionsPanel.java
similarity index 76%
rename from openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/AdminSavePanel.java
rename to openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/AdminActionsPanel.java
index 24098c4..8c0bed9 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/AdminSavePanel.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/AdminActionsPanel.java
@@ -19,22 +19,21 @@
 package org.apache.openmeetings.web.admin;
 
 import org.apache.openmeetings.web.common.ConfirmableAjaxBorder;
-import org.apache.openmeetings.web.common.FormSaveRefreshPanel;
+import org.apache.openmeetings.web.common.FormActionsPanel;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.ajax.markup.html.form.AjaxButton;
 import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.form.Form;
 import org.apache.wicket.model.Model;
 
-public abstract class AdminSavePanel<T> extends FormSaveRefreshPanel<T> {
+public abstract class AdminActionsPanel<T> extends FormActionsPanel<T> {
 	private static final long serialVersionUID = 1L;
 	private final Label newRecord = new Label("newRecord", Model.of(""));
 	private final Form<T> form;
 	private ConfirmableAjaxBorder delBtn;
-	private ConfirmableAjaxBorder purgeBtn;
 	private AjaxButton restoreBtn;
 
-	public AdminSavePanel(String id, final Form<T> form) {
+	public AdminActionsPanel(String id, final Form<T> form) {
 		super(id, form);
 		this.form = form;
 	}
@@ -60,7 +59,7 @@ public abstract class AdminSavePanel<T> extends FormSaveRefreshPanel<T> {
 			protected void onError(AjaxRequestTarget target) {
 				// repaint the feedback panel so errors are shown
 				target.add(feedback);
-				AdminSavePanel.this.onError(target, form);
+				AdminActionsPanel.this.onError(target, form);
 			}
 		};
 		// add a cancel button that can be used to submit the form via ajax
@@ -75,7 +74,7 @@ public abstract class AdminSavePanel<T> extends FormSaveRefreshPanel<T> {
 				// repaint the feedback panel so errors are shown
 				target.add(feedback);
 				setNewVisible(false);
-				AdminSavePanel.this.onError(target, form);
+				AdminActionsPanel.this.onError(target, form);
 			}
 
 			@Override
@@ -86,24 +85,6 @@ public abstract class AdminSavePanel<T> extends FormSaveRefreshPanel<T> {
 				onDeleteSubmit(target, form);
 			}
 		};
-		purgeBtn = new ConfirmableAjaxBorder("btn-purge", getString("80"), getString("833"), cForm) {
-			private static final long serialVersionUID = 1L;
-
-			@Override
-			protected void onSubmit(AjaxRequestTarget target) {
-				// repaint the feedback panel so that it is hidden
-				target.add(feedback);
-				setNewVisible(false);
-				onPurgeSubmit(target, form);
-			}
-
-			@Override
-			protected void onError(AjaxRequestTarget target) {
-				// repaint the feedback panel so errors are shown
-				target.add(feedback);
-				AdminSavePanel.this.onError(target, form);
-			}
-		};
 		restoreBtn = new AjaxButton("btn-restore", form) {
 			private static final long serialVersionUID = 1L;
 
@@ -119,11 +100,10 @@ public abstract class AdminSavePanel<T> extends FormSaveRefreshPanel<T> {
 			protected void onError(AjaxRequestTarget target) {
 				// repaint the feedback panel so errors are shown
 				target.add(feedback);
-				AdminSavePanel.this.onError(target, form);
+				AdminActionsPanel.this.onError(target, form);
 			}
 		};
 		add(newBtn, delBtn
-				, purgeBtn.setOutputMarkupPlaceholderTag(true).setVisible(false)
 				, restoreBtn.setOutputMarkupPlaceholderTag(true).setVisible(false));
 		super.onInitialize();
 	}
@@ -137,16 +117,11 @@ public abstract class AdminSavePanel<T> extends FormSaveRefreshPanel<T> {
 		delBtn.setVisible(visible);
 	}
 
-	public void setPurgeVisible(boolean visible) {
-		purgeBtn.setVisible(visible);
-	}
-
 	public void setRestoreVisible(boolean visible) {
 		restoreBtn.setVisible(visible);
 	}
 
 	protected abstract void onNewSubmit(AjaxRequestTarget target, Form<?> form);
 	protected abstract void onDeleteSubmit(AjaxRequestTarget target, Form<?> form);
-	protected abstract void onPurgeSubmit(AjaxRequestTarget target, Form<?> form);
 	protected abstract void onRestoreSubmit(AjaxRequestTarget target, Form<?> form);
 }
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/AdminBaseForm.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/AdminBaseForm.java
index 9fb1dc2..25478db 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/AdminBaseForm.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/AdminBaseForm.java
@@ -35,14 +35,14 @@ import org.apache.wicket.util.time.Duration;
  */
 public abstract class AdminBaseForm<T> extends Form<T> {
 	private static final long serialVersionUID = 1L;
-	private AdminSavePanel<T> savePanel;
+	private AdminActionsPanel<T> savePanel;
 	protected final AjaxFormValidatingBehavior validationBehavior
 			= new AjaxFormValidatingBehavior("keydown", Duration.ONE_SECOND);
 
 	public AdminBaseForm(String id, IModel<T> object) {
 		super(id, object);
 
-		savePanel = new AdminSavePanel<T>("buttons", this) {
+		savePanel = new AdminActionsPanel<T>("buttons", this) {
 			private static final long serialVersionUID = 1L;
 
 			@Override
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/users/UserForm.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/users/UserForm.java
index f179143..466432e 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/users/UserForm.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/admin/users/UserForm.java
@@ -27,10 +27,8 @@ import static org.apache.openmeetings.web.app.WebSession.getRights;
 import static org.apache.openmeetings.web.app.WebSession.getUserId;
 import static org.apache.wicket.validation.validator.StringValidator.minimumLength;
 
-import java.io.File;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -44,14 +42,11 @@ import org.apache.openmeetings.db.dao.server.OAuth2Dao;
 import org.apache.openmeetings.db.dao.user.UserDao;
 import org.apache.openmeetings.db.entity.server.LdapConfig;
 import org.apache.openmeetings.db.entity.server.OAuthServer;
-import org.apache.openmeetings.db.entity.user.Address;
-import org.apache.openmeetings.db.entity.user.AsteriskSipUser;
 import org.apache.openmeetings.db.entity.user.User;
 import org.apache.openmeetings.db.entity.user.User.Right;
 import org.apache.openmeetings.db.entity.user.User.Type;
 import org.apache.openmeetings.db.util.AuthLevelUtil;
 import org.apache.openmeetings.service.mail.EmailManager;
-import org.apache.openmeetings.util.OmFileHelper;
 import org.apache.openmeetings.web.admin.AdminBaseForm;
 import org.apache.openmeetings.web.common.ComunityUserForm;
 import org.apache.openmeetings.web.common.GeneralUserForm;
@@ -184,6 +179,12 @@ public class UserForm extends AdminBaseForm<User> {
 	}
 
 	@Override
+	protected void onConfigure() {
+		super.onConfigure();
+		onModelChanged();
+	}
+
+	@Override
 	protected void onModelChanged() {
 		super.onModelChanged();
 		boolean nd = !getModelObject().isDeleted();
@@ -206,23 +207,12 @@ public class UserForm extends AdminBaseForm<User> {
 
 	@Override
 	protected void onPurgeSubmit(AjaxRequestTarget target, Form<?> form) {
-		User u = getModelObject();
-		u.setDeleted(true);
-		u.setSipUser(new AsteriskSipUser());
-		u.setAddress(new Address());
-		u.setAge(new Date());
-		u.setExternalId(null);
-		final String purged = String.format("Purged %s", UUID.randomUUID());
-		u.setFirstname(purged);
-		u.setLastname(purged);
-		u.setLogin(purged);
-		File pic = OmFileHelper.getUserProfilePicture(u.getId(), u.getPictureuri(), null);
-		if (pic != null) {
-			pic.delete();
+		if (isAdminPassRequired()) {
+			adminPass.setAction((SerializableConsumer<AjaxRequestTarget>)t -> purgeUser(t));
+			adminPass.open(target);
+		} else {
+			purgeUser(target);
 		}
-		//u.
-		//User fields "age, externaluserid, firstname, lastname, login, pictureuri" will be replaced with "Purged_some_hash"
-		//onSaveSubmit(target, form);
 	}
 
 	@Override
@@ -245,6 +235,11 @@ public class UserForm extends AdminBaseForm<User> {
 		return checkLevel(u.getRights()) || (ou != null && checkLevel(ou.getRights()));
 	}
 
+	private void purgeUser(AjaxRequestTarget target) {
+		userDao.purge(getModelObject(), getUserId());
+		updateForm(target);
+	}
+
 	private void saveUser(AjaxRequestTarget target, String pass) {
 		User u = getModelObject();
 		final boolean isNew = u.getId() == null;
@@ -261,15 +256,19 @@ public class UserForm extends AdminBaseForm<User> {
 			String email = u.getAddress().getEmail();
 			emainManager.sendMail(login.getValue(), email, u.getActivatehash(), false, null);
 		}
-		setModelObject(userDao.get(u.getId()));
-		setNewVisible(false);
-		target.add(this, listContainer);
-		reinitJs(target);
+		updateForm(target);
 		if (u.getGroupUsers().isEmpty()) {
 			warning.open(target);
 		}
 	}
 
+	private void updateForm(AjaxRequestTarget target) {
+		setModelObject(userDao.get(getModelObject().getId()));
+		setNewVisible(false);
+		target.add(this, listContainer);
+		reinitJs(target);
+	}
+
 	@Override
 	protected void onNewSubmit(AjaxRequestTarget target, Form<?> form) {
 		setModelObject(getNewUserInstance(userDao.get(getUserId())));
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/ConfirmableAjaxBorder.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/ConfirmableAjaxBorder.java
index c4c72cd..e7bf7b9 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/ConfirmableAjaxBorder.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/ConfirmableAjaxBorder.java
@@ -23,6 +23,7 @@ import static org.apache.openmeetings.web.common.BasePanel.EVT_CLICK;
 import org.apache.wicket.ajax.AjaxEventBehavior;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
+import org.apache.wicket.ajax.form.AjaxFormSubmitBehavior;
 import org.apache.wicket.markup.html.border.Border;
 import org.apache.wicket.markup.html.form.Form;
 import org.apache.wicket.markup.html.panel.EmptyPanel;
@@ -39,7 +40,9 @@ public abstract class ConfirmableAjaxBorder extends Border {
 	private static final long serialVersionUID = 1L;
 	private static final String DIALOG_ID = "dialog";
 	protected final Form<?> form = new Form<>("form");
+	protected final Form<?> userForm;
 	private final ConfirmableBorderDialog dialog;
+	private boolean validate = false;
 
 	public ConfirmableAjaxBorder(String id, String title, String message) {
 		this(id, title, message, null, null);
@@ -54,6 +57,10 @@ public abstract class ConfirmableAjaxBorder extends Border {
 	}
 
 	public ConfirmableAjaxBorder(String id, String title, String message, Form<?> userForm, ConfirmableBorderDialog dialog) {
+		this(id, title, message, userForm, dialog, false);
+	}
+
+	public ConfirmableAjaxBorder(String id, String title, String message, Form<?> userForm, ConfirmableBorderDialog dialog, boolean validate) {
 		super(id, Model.of(message));
 		if (dialog == null) {
 			this.dialog = new ConfirmableBorderDialog(DIALOG_ID, title, message, userForm == null ? form : userForm);
@@ -62,6 +69,8 @@ public abstract class ConfirmableAjaxBorder extends Border {
 			this.dialog = dialog;
 			form.add(new EmptyPanel(DIALOG_ID));
 		}
+		this.userForm = userForm;
+		this.validate = validate;
 		this.dialog.setSubmitHandler((SerializableConsumer<AjaxRequestTarget>)t->onSubmit(t));
 		this.dialog.setErrorHandler((SerializableConsumer<AjaxRequestTarget>)t->onError(t));
 		setOutputMarkupId(true);
@@ -74,22 +83,38 @@ public abstract class ConfirmableAjaxBorder extends Border {
 	@Override
 	protected void onInitialize() {
 		super.onInitialize();
-		add(new AjaxEventBehavior(EVT_CLICK) {
-			private static final long serialVersionUID = 1L;
-
-			@Override
-			protected void updateAjaxAttributes(AjaxRequestAttributes attributes) {
-				super.updateAjaxAttributes(attributes);
-				ConfirmableAjaxBorder.this.updateAjaxAttributes(attributes);
-			}
+		if (validate) {
+			add(new AjaxFormSubmitBehavior(EVT_CLICK) {
+				private static final long serialVersionUID = 1L;
 
-			@Override
-			protected void onEvent(AjaxRequestTarget target) {
-				if (isClickable()) {
+				@Override
+				protected void onSubmit(AjaxRequestTarget target) {
 					dialog.open(target);
 				}
-			}
-		});
+
+				@Override
+				protected void onError(AjaxRequestTarget target) {
+					ConfirmableAjaxBorder.this.onError(target);
+				}
+			});
+		} else {
+			add(new AjaxEventBehavior(EVT_CLICK) {
+				private static final long serialVersionUID = 1L;
+
+				@Override
+				protected void updateAjaxAttributes(AjaxRequestAttributes attributes) {
+					super.updateAjaxAttributes(attributes);
+					ConfirmableAjaxBorder.this.updateAjaxAttributes(attributes);
+				}
+
+				@Override
+				protected void onEvent(AjaxRequestTarget target) {
+					if (isClickable()) {
+						dialog.open(target);
+					}
+				}
+			});
+		}
 		addToBorder(form);
 	}
 
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/FormSaveRefreshPanel.html b/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/FormActionsPanel.html
similarity index 79%
rename from openmeetings-web/src/main/java/org/apache/openmeetings/web/common/FormSaveRefreshPanel.html
rename to openmeetings-web/src/main/java/org/apache/openmeetings/web/common/FormActionsPanel.html
index 6790cbb..6578d94 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/FormSaveRefreshPanel.html
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/FormActionsPanel.html
@@ -21,13 +21,16 @@
 <html xmlns:wicket="http://wicket.apache.org">
 <wicket:panel>
 	<div class="formSaveBar ui-widget-header">
-		<div class="ui-button ui-widget ui-corner-all ui-button-icon-only" wicket:id="ajax-save-button" wicket:message="title:144">
+		<div class="ui-button ui-widget ui-corner-all ui-button-icon-only" wicket:id="btn-save" wicket:message="title:144">
 			<span class="ui-button-icon ui-icon ui-icon-disk"></span>&nbsp;
 		</div>
-		<div class="ui-button ui-widget ui-corner-all ui-button-icon-only" wicket:id="ajax-refresh-button" wicket:message="title:lbl.refresh">
+		<div class="ui-button ui-widget ui-corner-all ui-button-icon-only" wicket:id="btn-refresh" wicket:message="title:lbl.refresh">
 			<span class="ui-button-icon ui-icon ui-icon-refresh"></span>&nbsp;
 		</div>
 		<wicket:child/>
+		<div class="ui-button ui-widget ui-corner-all ui-button-icon-only ui-state-error" wicket:id="btn-purge" wicket:message="title:admin.purge">
+			<span class="ui-button-icon ui-icon ui-icon-trash"></span>&nbsp;
+		</div>
 	</div>
 	<div wicket:id="feedback" class="error adminFormsFeedbackPanel"></div>
 </wicket:panel>
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/FormSaveRefreshPanel.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/FormActionsPanel.java
similarity index 73%
rename from openmeetings-web/src/main/java/org/apache/openmeetings/web/common/FormSaveRefreshPanel.java
rename to openmeetings-web/src/main/java/org/apache/openmeetings/web/common/FormActionsPanel.java
index cca36cf..142cb99 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/FormSaveRefreshPanel.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/common/FormActionsPanel.java
@@ -26,13 +26,14 @@ import org.apache.wicket.markup.html.panel.Panel;
 import com.googlecode.wicket.jquery.core.Options;
 import com.googlecode.wicket.kendo.ui.panel.KendoFeedbackPanel;
 
-public abstract class FormSaveRefreshPanel<T> extends Panel {
+public abstract class FormActionsPanel<T> extends Panel {
 	private static final long serialVersionUID = 1L;
 	private final Form<T> form;
 	protected final KendoFeedbackPanel feedback = new KendoFeedbackPanel("feedback", new Options("button", true));
 	private AjaxButton saveBtn;
+	private ConfirmableAjaxBorder purgeBtn;
 
-	public FormSaveRefreshPanel(String id, Form<T> form) {
+	public FormActionsPanel(String id, Form<T> form) {
 		super(id);
 		this.form = form;
 		setOutputMarkupId(true);
@@ -43,7 +44,7 @@ public abstract class FormSaveRefreshPanel<T> extends Panel {
 		add(feedback.setOutputMarkupId(true));
 
 		// add a save button that can be used to submit the form via ajax
-		add(saveBtn = new AjaxButton("ajax-save-button", form) {
+		add(saveBtn = new AjaxButton("btn-save", form) {
 			private static final long serialVersionUID = 1L;
 
 			@Override
@@ -57,12 +58,12 @@ public abstract class FormSaveRefreshPanel<T> extends Panel {
 			protected void onError(AjaxRequestTarget target) {
 				// repaint the feedback panel so errors are shown
 				target.add(feedback);
-				FormSaveRefreshPanel.this.onError(target, form);
+				FormActionsPanel.this.onError(target, form);
 			}
 		});
 
 		// add a refresh button that can be used to submit the form via ajax
-		add(new AjaxButton("ajax-refresh-button", form) {
+		add(new AjaxButton("btn-refresh", form) {
 			private static final long serialVersionUID = 1L;
 
 			@Override
@@ -78,9 +79,28 @@ public abstract class FormSaveRefreshPanel<T> extends Panel {
 				// repaint the feedback panel so errors are shown
 				target.add(feedback);
 				setNewVisible(false);
-				FormSaveRefreshPanel.this.onError(target, form);
+				FormActionsPanel.this.onError(target, form);
 			}
 		});
+		purgeBtn = new ConfirmableAjaxBorder("btn-purge", getString("80"), getString("833"), form, null, true) {
+			private static final long serialVersionUID = 1L;
+
+			@Override
+			protected void onSubmit(AjaxRequestTarget target) {
+				// repaint the feedback panel so that it is hidden
+				target.add(feedback);
+				setNewVisible(false);
+				onPurgeSubmit(target, form);
+			}
+
+			@Override
+			protected void onError(AjaxRequestTarget target) {
+				// repaint the feedback panel so errors are shown
+				target.add(feedback);
+				FormActionsPanel.this.onError(target, form);
+			}
+		};
+		add(purgeBtn.setOutputMarkupPlaceholderTag(true).setVisible(false));
 		super.onInitialize();
 	}
 
@@ -97,7 +117,13 @@ public abstract class FormSaveRefreshPanel<T> extends Panel {
 		// for admin only, will be implemented in admin
 	}
 
+	public void setPurgeVisible(boolean visible) {
+		purgeBtn.setVisible(visible);
+	}
+
 	protected abstract void onSaveSubmit(AjaxRequestTarget target, Form<?> form);
+	protected abstract void onRefreshSubmit(AjaxRequestTarget target, Form<?> form);
+	protected abstract void onPurgeSubmit(AjaxRequestTarget target, Form<?> form);
 
 	/**
 	 * Save error handler
@@ -108,6 +134,4 @@ public abstract class FormSaveRefreshPanel<T> extends Panel {
 	protected void onError(AjaxRequestTarget target, Form<?> form) {
 		//no-op
 	}
-
-	protected abstract void onRefreshSubmit(AjaxRequestTarget target, Form<?> form);
 }
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/profile/ProfileForm.java b/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/profile/ProfileForm.java
index 30bbba4..280d487 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/profile/ProfileForm.java
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/user/profile/ProfileForm.java
@@ -23,8 +23,10 @@ import static org.apache.openmeetings.web.common.BasePanel.EVT_CLICK;
 
 import org.apache.openmeetings.db.dao.user.UserDao;
 import org.apache.openmeetings.db.entity.user.User;
+import org.apache.openmeetings.web.app.Application;
+import org.apache.openmeetings.web.app.WebSession;
 import org.apache.openmeetings.web.common.ComunityUserForm;
-import org.apache.openmeetings.web.common.FormSaveRefreshPanel;
+import org.apache.openmeetings.web.common.FormActionsPanel;
 import org.apache.openmeetings.web.common.GeneralUserForm;
 import org.apache.openmeetings.web.common.UploadableProfileImagePanel;
 import org.apache.wicket.ajax.AjaxEventBehavior;
@@ -51,6 +53,8 @@ public class ProfileForm extends Form<User> {
 	private final PasswordTextField passwd = new PasswordTextField("passwd", new Model<String>());
 	private final GeneralUserForm userForm;
 	private final ChangePasswordDialog chPwdDlg;
+	private FormActionsPanel<User> actions;
+
 	@SpringBean
 	private UserDao userDao;
 
@@ -66,7 +70,7 @@ public class ProfileForm extends Form<User> {
 		super.onInitialize();
 		add(passwd.setLabel(Model.of(getString("current.password"))).setRequired(true));
 
-		add(new FormSaveRefreshPanel<User>("buttons", this) {
+		add(actions = new FormActionsPanel<User>("buttons", this) {
 			private static final long serialVersionUID = 1L;
 
 			private void refreshUser() {
@@ -95,6 +99,13 @@ public class ProfileForm extends Form<User> {
 				refreshUser();
 				target.add(ProfileForm.this);
 			}
+
+			@Override
+			protected void onPurgeSubmit(AjaxRequestTarget target, Form<?> form) {
+				userDao.purge(getModelObject(), getUserId());
+				WebSession.get().invalidateNow();
+				setResponsePage(Application.get().getSignInPageClass());
+			}
 		});
 		add(new WebMarkupContainer("changePwd").add(new ButtonBehavior("#changePwd"), new AjaxEventBehavior(EVT_CLICK) {
 			private static final long serialVersionUID = 1L;
@@ -114,6 +125,12 @@ public class ProfileForm extends Form<User> {
 	}
 
 	@Override
+	protected void onConfigure() {
+		super.onConfigure();
+		actions.setPurgeVisible(true);
+	}
+
+	@Override
 	protected void onValidate() {
 		String p = passwd.getConvertedInput();
 		if (!Strings.isEmpty(p) && !userDao.verifyPassword(getModelObject().getId(), p)) {

-- 
To stop receiving notification emails like this one, please contact
solomax@apache.org.