From 6deecf459e84c5ee3e88fb89714adbe50f09017e Mon Sep 17 00:00:00 2001 From: srosse <none@none> Date: Mon, 24 Oct 2016 15:34:45 +0200 Subject: [PATCH] OO-2168: enable password protected of alias in forums --- .../java/org/olat/modules/fo/Pseudonym.java | 42 ++++ .../fo/_i18n/LocalStrings_de.properties | 12 ++ .../fo/_i18n/LocalStrings_en.properties | 8 + .../olat/modules/fo/manager/ForumManager.java | 123 ++++++++++++ .../olat/modules/fo/model/PseudonymImpl.java | 132 +++++++++++++ .../modules/fo/model/PseudonymStatistics.java | 59 ++++++ .../modules/fo/ui/ForumAdminController.java | 97 ++------- .../fo/ui/ForumPseudonymsAdminController.java | 187 ++++++++++++++++++ .../fo/ui/ForumPseudonymsDataModel.java | 99 ++++++++++ .../fo/ui/ForumSettingsAdminController.java | 115 +++++++++++ .../modules/fo/ui/MessageEditController.java | 101 +++++++++- .../modules/fo/ui/NewPseudonymController.java | 134 +++++++++++++ .../olat/modules/fo/ui/_content/admin.html | 2 + .../fo/ui/_content/admin_pseudonyms.html | 8 + src/main/resources/META-INF/persistence.xml | 1 + .../database/mysql/alter_11_x_0_to_11_1_0.sql | 12 ++ .../database/mysql/setupDatabase.sql | 13 ++ .../oracle/alter_11_x_0_to_11_1_0.sql | 12 ++ .../database/oracle/setupDatabase.sql | 13 +- .../postgresql/alter_11_x_0_to_11_1_0.sql | 12 ++ .../database/postgresql/setupDatabase.sql | 12 ++ .../org/olat/modules/fo/ForumManagerTest.java | 183 +++++++++++++++++ 22 files changed, 1292 insertions(+), 85 deletions(-) create mode 100644 src/main/java/org/olat/modules/fo/Pseudonym.java create mode 100644 src/main/java/org/olat/modules/fo/model/PseudonymImpl.java create mode 100644 src/main/java/org/olat/modules/fo/model/PseudonymStatistics.java create mode 100644 src/main/java/org/olat/modules/fo/ui/ForumPseudonymsAdminController.java create mode 100644 src/main/java/org/olat/modules/fo/ui/ForumPseudonymsDataModel.java create mode 100644 src/main/java/org/olat/modules/fo/ui/ForumSettingsAdminController.java create mode 100644 src/main/java/org/olat/modules/fo/ui/NewPseudonymController.java create mode 100644 src/main/java/org/olat/modules/fo/ui/_content/admin.html create mode 100644 src/main/java/org/olat/modules/fo/ui/_content/admin_pseudonyms.html create mode 100644 src/main/resources/database/mysql/alter_11_x_0_to_11_1_0.sql create mode 100644 src/main/resources/database/oracle/alter_11_x_0_to_11_1_0.sql create mode 100644 src/main/resources/database/postgresql/alter_11_x_0_to_11_1_0.sql diff --git a/src/main/java/org/olat/modules/fo/Pseudonym.java b/src/main/java/org/olat/modules/fo/Pseudonym.java new file mode 100644 index 00000000000..4720784760b --- /dev/null +++ b/src/main/java/org/olat/modules/fo/Pseudonym.java @@ -0,0 +1,42 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.fo; + +import org.olat.core.id.CreateInfo; + +/** + * + * Initial date: 19 oct. 2016<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public interface Pseudonym extends CreateInfo { + + public Long getKey(); + + public String getPseudonym(); + + public String getSalt(); + + public String getCredential(); + + public String getAlgorithm(); + +} diff --git a/src/main/java/org/olat/modules/fo/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/modules/fo/_i18n/LocalStrings_de.properties index bc50f285e85..cf3e8b1f12d 100644 --- a/src/main/java/org/olat/modules/fo/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/modules/fo/_i18n/LocalStrings_de.properties @@ -1,6 +1,8 @@ #Mon Mar 02 09:54:04 CET 2009 admin.menu.title=Forum admin.menu.title.alt=Konfiguration Forum +admin.pseudonyms.title=Pseudonym verwalten +admin.pseudonyms.descr=Pseudonym verwalten admin.title=Konfiguration Forum anonymous.course.default=Standardeinstellung f\u00FCr Kursforum anonymous.default.enabled=Pseudonym aktiviert @@ -22,11 +24,16 @@ attachments.upload.successful=Die Datei {0} wurde erfolgreich hochgeladen. Bei B attachments.error.file.exists=Diese Datei existiert bereits und kann nicht erneut hochgeladen werden. attachments.remove.string=l\u00f6schen close.thread=Diskussion beenden +confirm.delete.pseudonym.title=Pseudonym l\u00f6schen +confirm.detele.pseudonym.msg=Wollen Sie wirklich den Pseudonym "{0}" mit {1} Forum Beitr\u00E4ge l\u00f6schen? +create.pseudonym=Pseudonym erstellen delete.att.ok=Die Datei wurde gel\u00F6scht. deleteok=Der Beitrag wurde gel\u00F6scht error.message.deleted=$\:deleteok error.field.not.empty=Dieses Feld darf nicht leer sein. error.pseudonym=Zu grosse \u00C4hnlichkeit zu bereits bestehendem Benutzer +error.pseudonym.authentication=Authentication failed! +error.pseudonym.protected=Pseudonym ist gesch\u00FCtzt und darf nicht ohne Passwort ben\u00FCtzen werde. filter=Personenfilter filter.header.title=Nach Personen suchen filter.header.allUsers=Beitr\u00E4ge aller Personen @@ -77,6 +84,9 @@ msg.title.re=Re\: msg.update=Editieren msg.upload=Datei anh\u00E4ngen natural.sort=Verschaltet +new.pseudonym=Neue gesch\u00FCtzte Pseudonym erstellen +new.password.label=Passwort +new.pseudonym.label=Pseudonym no=Nein notifications.entry=Nachricht "{0}" von {1} erstellt notifications.entry.modified=Nachricht "{0}" von {1} ver\u00E4ndert @@ -84,6 +94,8 @@ notifications.header=In einem von Ihnen abonnierten Forum befinden sich neue Nac notifications.header.course=Forum in Kurs "{0}" notifications.header.group=Forum in Gruppe "{0}" open.thread=Diskussion \u00F6ffnen +password=Passwort f\u00FCr die sp\u00E4tere Verwendung Ihres Pseudonyms (wahlfrei) +password.placeholder=Passwort f\u00FCr exklusive Verwendung Ihres Pseudonyms pseudonym= pseudonym.suffix=(Pseudonym) reallydeleteAtt=Wollen Sie diese Datei wirklich l\u00F6schen? diff --git a/src/main/java/org/olat/modules/fo/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/modules/fo/_i18n/LocalStrings_en.properties index 32debca6fd3..03ce6809c9d 100644 --- a/src/main/java/org/olat/modules/fo/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/modules/fo/_i18n/LocalStrings_en.properties @@ -1,6 +1,8 @@ #Sat Jan 22 13:41:44 CET 2011 admin.menu.title=Forum admin.menu.title.alt=Forum configuration +admin.pseudonyms.title=Manage forum aliases +admin.pseudonyms.descr=Create or delete aliases used in forum posts to make anonymous posts. When creating an alias, the system required the correct password to be entered when reusing the alias. when deleting an existing alias, the system allow reusing the alias by anybody. Note, deleting the alias here does NOT delete any aliases in the forums. This is only a password protection lookup table. the actual used alias is stored in each forum posting. admin.title=Forum configuration anonymous.course.default=Default for course forum anonymous.default.enabled=Alias enabled @@ -20,6 +22,7 @@ attachments.remove.string=Delete attachments.too.big=Attachment too large. A maximum of {0} MB is allowed. attachments.upload.successful=File {0} successfully uploaded. Other files can still be attached if needed. close.thread=Close discussion +create.pseudonym=Create alias delete.att.ok=File deleted. deleteok=This post has been deleted. error.message.deleted=$\:deleteok @@ -80,6 +83,9 @@ msg.title.re=Re\: msg.update=Edit msg.upload=Attach file natural.sort=Thread +new.pseudonym=Create password protected alias +new.password.label=Password +new.pseudonym.label=Alias no=No notifications.entry=Message "{0}" created by {1} notifications.entry.modified=Message "{0}" modified by {1} @@ -87,6 +93,8 @@ notifications.header=There are new messages in a forum subscribed by you\: notifications.header.course=Forum in course "{0}" notifications.header.group=Forum in group "{0}" open.thread=Open discussion +password=Password for later reuse of pseudonym (optional) +password.placeholder=Password for exclusive use of your alias pseudonym= pseudonym.suffix=(Alias) reallydeleteAtt=Do you really want to delete this file? diff --git a/src/main/java/org/olat/modules/fo/manager/ForumManager.java b/src/main/java/org/olat/modules/fo/manager/ForumManager.java index 0b1cd251ecd..ca1b61ebb77 100644 --- a/src/main/java/org/olat/modules/fo/manager/ForumManager.java +++ b/src/main/java/org/olat/modules/fo/manager/ForumManager.java @@ -43,6 +43,7 @@ import org.olat.basesecurity.IdentityRef; import org.olat.core.commons.modules.bc.FolderConfig; import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl; import org.olat.core.commons.persistence.DB; +import org.olat.core.commons.persistence.PersistenceHelper; import org.olat.core.commons.services.mark.MarkingService; import org.olat.core.commons.services.mark.impl.MarkImpl; import org.olat.core.commons.services.text.TextService; @@ -51,15 +52,20 @@ import org.olat.core.id.OLATResourceable; import org.olat.core.logging.AssertException; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; +import org.olat.core.util.Encoder; +import org.olat.core.util.Encoder.Algorithm; +import org.olat.core.util.StringHelper; import org.olat.core.util.coordinate.CoordinatorManager; import org.olat.core.util.resource.OresHelper; import org.olat.core.util.vfs.VFSContainer; import org.olat.core.util.vfs.VFSItem; +import org.olat.login.LoginModule; import org.olat.modules.fo.Forum; import org.olat.modules.fo.ForumChangedEvent; import org.olat.modules.fo.Message; import org.olat.modules.fo.MessageLight; import org.olat.modules.fo.MessageRef; +import org.olat.modules.fo.Pseudonym; import org.olat.modules.fo.QuoteAndTagFilter; import org.olat.modules.fo.Status; import org.olat.modules.fo.model.ForumImpl; @@ -68,6 +74,8 @@ import org.olat.modules.fo.model.ForumUserStatistics; import org.olat.modules.fo.model.MessageImpl; import org.olat.modules.fo.model.MessageLightImpl; import org.olat.modules.fo.model.MessageStatistics; +import org.olat.modules.fo.model.PseudonymImpl; +import org.olat.modules.fo.model.PseudonymStatistics; import org.olat.modules.fo.model.ReadMessageImpl; import org.olat.modules.fo.ui.MessagePeekview; import org.olat.user.UserManager; @@ -86,6 +94,8 @@ public class ForumManager { @Autowired private DB dbInstance; @Autowired + private LoginModule loginModule; + @Autowired private TextService txtService; @Autowired private UserManager userManager; @@ -350,6 +360,90 @@ public class ForumManager { .getResultList(); return messages == null || messages.isEmpty() ? null : messages.get(0); } + + public boolean isPseudonymProtected(String pseudonym) { + StringBuilder query = new StringBuilder(); + query.append("select pseudonym.key from fopseudonym as pseudo") + .append(" where lower(pseudo.pseudonym)=:pseudonym"); + + List<Long> pseudonyms = dbInstance.getCurrentEntityManager() + .createQuery(query.toString(), Long.class) + .setParameter("pseudonym", pseudonym.toLowerCase()) + .setFirstResult(0) + .setMaxResults(1) + .getResultList(); + return pseudonyms != null && pseudonyms.size() > 0 && pseudonyms.get(0) != null; + } + + public boolean isPseudonymInUseInForums(String pseudonym) { + StringBuilder query = new StringBuilder(); + query.append("select msg.key from fomessage as msg") + .append(" where lower(msg.pseudonym)=:pseudonym"); + + List<Long> pseudonyms = dbInstance.getCurrentEntityManager() + .createQuery(query.toString(), Long.class) + .setParameter("pseudonym", pseudonym.toLowerCase()) + .setFirstResult(0) + .setMaxResults(1) + .getResultList(); + return pseudonyms != null && pseudonyms.size() > 0 && pseudonyms.get(0) != null; + } + + public List<Pseudonym> getPseudonyms(String pseudonym) { + StringBuilder query = new StringBuilder(); + query.append("select pseudo from fopseudonym as pseudo") + .append(" where lower(pseudo.pseudonym)=:pseudonym"); + + return dbInstance.getCurrentEntityManager() + .createQuery(query.toString(), Pseudonym.class) + .setParameter("pseudonym", pseudonym.toLowerCase()) + .getResultList(); + } + + public boolean authenticatePseudonym(Pseudonym pseudonym, String password) { + if(pseudonym.getAlgorithm() != null) { + //check if update is needed + Algorithm algorithm = Algorithm.valueOf(pseudonym.getAlgorithm()); + String credentials = Encoder.encrypt(password, pseudonym.getSalt(), algorithm); + return credentials.equals(pseudonym.getCredential()); + } + return false; + } + + public Pseudonym createProtectedPseudonym(String pseudonym, String password) { + PseudonymImpl pseudo = new PseudonymImpl(); + pseudo.setCreationDate(new Date()); + pseudo.setPseudonym(pseudonym); + + Algorithm algorithm = loginModule.getDefaultHashAlgorithm(); + String salt = algorithm.isSalted() ? Encoder.getSalt() : null; + String newCredentials = Encoder.encrypt(password, salt, algorithm); + pseudo.setSalt(salt); + pseudo.setCredential(newCredentials); + pseudo.setAlgorithm(algorithm.name()); + + dbInstance.getCurrentEntityManager().persist(pseudo); + return pseudo; + } + + + public Pseudonym getPseudonymByKey(Long key) { + StringBuilder query = new StringBuilder(); + query.append("select pseudo from fopseudonym as pseudo") + .append(" where pseudo.key=:pseudonymKey"); + + List<Pseudonym> pseudonyms = dbInstance.getCurrentEntityManager() + .createQuery(query.toString(), Pseudonym.class) + .setParameter("pseudonymKey", key) + .getResultList(); + return pseudonyms.size() > 0 ? pseudonyms.get(0) : null; + } + + public void deletePseudonym(Pseudonym pseudonym) { + Pseudonym reloadedPseudonym = dbInstance.getCurrentEntityManager() + .getReference(PseudonymImpl.class, pseudonym.getKey()); + dbInstance.getCurrentEntityManager().remove(reloadedPseudonym); + } public String getPseudonym(Forum forum, IdentityRef identity) { StringBuilder query = new StringBuilder(); @@ -364,6 +458,35 @@ public class ForumManager { return pseudonyms == null || pseudonyms.isEmpty() ? null : pseudonyms.get(0); } + public List<PseudonymStatistics> getPseudonymStatistics(String searchString) { + StringBuilder sb = new StringBuilder(); + sb.append("select pseudo.key, pseudo.creationDate, pseudo.pseudonym, count(msg.key)") + .append(" from fopseudonym as pseudo") + .append(" left join fomessage as msg on (msg.pseudonym=pseudo.pseudonym)"); + if(StringHelper.containsNonWhitespace(searchString)) { + sb.append(" where "); + PersistenceHelper.appendFuzzyLike(sb, "pseudo.pseudonym", "pseudonym", dbInstance.getDbVendor()); + } + sb.append(" group by pseudo.key, pseudo.creationDate, pseudo.pseudonym"); + + TypedQuery<Object[]> query = dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), Object[].class); + if(StringHelper.containsNonWhitespace(searchString)) { + query.setParameter("pseudonym", PersistenceHelper.makeFuzzyQueryString(searchString)); + } + + List<Object[]> objects = query.getResultList(); + List<PseudonymStatistics> stats = new ArrayList<>(objects.size()); + for(Object[] object:objects) { + Long key = (Long)object[0]; + Date creationDate = (Date)object[1]; + String pseudonym = (String)object[2]; + Long numOfMessages = PersistenceHelper.extractLong(object, 3); + stats.add(new PseudonymStatistics(key, creationDate, pseudonym, numOfMessages)); + } + return stats; + } + public List<MessageLight> getLightMessagesByForum(Forum forum) { StringBuilder query = new StringBuilder(); query.append("select msg from folightmessage as msg") diff --git a/src/main/java/org/olat/modules/fo/model/PseudonymImpl.java b/src/main/java/org/olat/modules/fo/model/PseudonymImpl.java new file mode 100644 index 00000000000..199677792d5 --- /dev/null +++ b/src/main/java/org/olat/modules/fo/model/PseudonymImpl.java @@ -0,0 +1,132 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.fo.model; + +import java.util.Date; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.olat.core.id.Persistable; +import org.olat.modules.fo.Pseudonym; + +/** + * + * Initial date: 19 oct. 2016<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +@Entity(name="fopseudonym") +@Table(name="o_forum_pseudonym") +public class PseudonymImpl implements Pseudonym, Persistable { + + private static final long serialVersionUID = 8818430235631413863L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name="id", nullable=false, unique=true, insertable=true, updatable=false) + private Long key; + + @Temporal(TemporalType.TIMESTAMP) + @Column(name="creationdate", nullable=false, insertable=true, updatable=false) + private Date creationDate; + + @Column(name="p_pseudonym", nullable=false, insertable=true, updatable=false) + private String pseudonym; + @Column(name="p_credential", nullable=false, insertable=true, updatable=false) + private String credential; + @Column(name="p_salt", nullable=false, insertable=true, updatable=false) + private String salt; + @Column(name="p_hashalgorithm", nullable=false, insertable=true, updatable=false) + private String algorithm; + + @Override + public Long getKey() { + return key; + } + + public Date getCreationDate() { + return creationDate; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + public String getPseudonym() { + return pseudonym; + } + + public void setPseudonym(String pseudonym) { + this.pseudonym = pseudonym; + } + + public String getCredential() { + return credential; + } + + public void setCredential(String credential) { + this.credential = credential; + } + + public String getSalt() { + return salt; + } + + public void setSalt(String salt) { + this.salt = salt; + } + + public String getAlgorithm() { + return algorithm; + } + + public void setAlgorithm(String algorithm) { + this.algorithm = algorithm; + } + + @Override + public int hashCode() { + return key == null ? 43126 : key.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if(this == obj) { + return true; + } + if(obj instanceof PseudonymImpl) { + PseudonymImpl pseudo = (PseudonymImpl)obj; + return key != null && key.equals(pseudo.getKey()); + } + return super.equals(obj); + } + + @Override + public boolean equalsByPersistableKey(Persistable persistable) { + return equals(persistable); + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/modules/fo/model/PseudonymStatistics.java b/src/main/java/org/olat/modules/fo/model/PseudonymStatistics.java new file mode 100644 index 00000000000..b6f5f8df039 --- /dev/null +++ b/src/main/java/org/olat/modules/fo/model/PseudonymStatistics.java @@ -0,0 +1,59 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.fo.model; + +import java.util.Date; + +/** + * + * Initial date: 19 oct. 2016<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class PseudonymStatistics { + + private final Long key; + private final Date creationDate; + private final String pseudonym; + private final Long numOfMessages; + + public PseudonymStatistics(Long key, Date creationDate, String pseudonym, Long numOfMessages) { + this.key = key; + this.creationDate = creationDate; + this.pseudonym = pseudonym; + this.numOfMessages = numOfMessages; + } + + public Long getKey() { + return key; + } + + public Date getCreationDate() { + return creationDate; + } + + public String getPseudonym() { + return pseudonym; + } + + public Long getNumOfMessages() { + return numOfMessages; + } +} diff --git a/src/main/java/org/olat/modules/fo/ui/ForumAdminController.java b/src/main/java/org/olat/modules/fo/ui/ForumAdminController.java index 57b45124ffb..a0bd351e507 100644 --- a/src/main/java/org/olat/modules/fo/ui/ForumAdminController.java +++ b/src/main/java/org/olat/modules/fo/ui/ForumAdminController.java @@ -20,18 +20,11 @@ package org.olat.modules.fo.ui; import org.olat.core.gui.UserRequest; -import org.olat.core.gui.components.form.flexible.FormItem; -import org.olat.core.gui.components.form.flexible.FormItemContainer; -import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement; -import org.olat.core.gui.components.form.flexible.elements.SingleSelection; -import org.olat.core.gui.components.form.flexible.impl.FormBasicController; -import org.olat.core.gui.components.form.flexible.impl.FormEvent; -import org.olat.core.gui.control.Controller; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.velocity.VelocityContainer; +import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; -import org.olat.core.util.Util; -import org.olat.modules.fo.Forum; -import org.olat.modules.fo.ForumModule; -import org.springframework.beans.factory.annotation.Autowired; +import org.olat.core.gui.control.controller.BasicController; /** * @@ -39,88 +32,32 @@ import org.springframework.beans.factory.annotation.Autowired; * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ -public class ForumAdminController extends FormBasicController { - - private static final String[] anonymousPostingKeys = new String[]{ "on" }; - private static final String[] defaultKeys = new String[]{ "enabled", "disabled" }; - - private MultipleSelectionElement anonymousPostingEl; - private SingleSelection defaultCourseEl, defaultGroupEl; - - @Autowired - private ForumModule forumModule; - +public class ForumAdminController extends BasicController { + public ForumAdminController(UserRequest ureq, WindowControl wControl) { super(ureq, wControl); - setTranslator(Util.createPackageTranslator(Forum.class, getLocale(), getTranslator())); - initForm(ureq); - } - - @Override - protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { - setFormTitle("admin.title"); + VelocityContainer mainVC = createVelocityContainer("admin"); - String[] anonymousPostingValues = new String[]{ "" }; - anonymousPostingEl = uifactory.addCheckboxesHorizontal("anonymous.posting", "anonymous.posting", formLayout, - anonymousPostingKeys, anonymousPostingValues); - if(forumModule.isAnonymousPostingWithPseudonymEnabled()) { - anonymousPostingEl.select(anonymousPostingKeys[0], true); - } - anonymousPostingEl.addActionListener(FormEvent.ONCHANGE); + ForumSettingsAdminController settingsCtrl = new ForumSettingsAdminController(ureq, getWindowControl()); + listenTo(settingsCtrl); + mainVC.put("settings", settingsCtrl.getInitialComponent()); - String[] defaultValues = new String[]{ - translate("anonymous.default.enabled"), translate("anonymous.default.disabled") - }; - defaultCourseEl = uifactory.addRadiosHorizontal("anonymous.course.default", "anonymous.course.default", formLayout, - defaultKeys, defaultValues); - if(forumModule.isPseudonymForCourseEnabledByDefault()) { - defaultCourseEl.select(defaultKeys[0], true); - } else { - defaultCourseEl.select(defaultKeys[1], true); - } - defaultCourseEl.addActionListener(FormEvent.ONCHANGE); - /* - defaultGroupEl = uifactory.addRadiosHorizontal("anonymous.group.default", "anonymous.group.default", formLayout, - defaultKeys, defaultValues); - if(forumModule.isPseudonymForGroupEnabledByDefault()) { - defaultGroupEl.select(defaultKeys[0], true); - } else { - defaultGroupEl.select(defaultKeys[1], true); - } - defaultGroupEl.addActionListener(FormEvent.ONCHANGE); - */ + ForumPseudonymsAdminController pseudonymsCtrl = new ForumPseudonymsAdminController(ureq, getWindowControl()); + listenTo(pseudonymsCtrl); + mainVC.put("pseudonyms", pseudonymsCtrl.getInitialComponent()); - updateUI(); + putInitialPanel(mainVC); } - - private void updateUI() { - defaultCourseEl.setVisible(anonymousPostingEl.isAtLeastSelected(1)); - //defaultGroupEl.setVisible(anonymousPostingEl.isAtLeastSelected(1)); - } - + @Override protected void doDispose() { // } @Override - protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { - if(anonymousPostingEl == source) { - forumModule.setAnonymousPostingWithPseudonymEnabled(anonymousPostingEl.isAtLeastSelected(1)); - updateUI(); - } else if(defaultGroupEl == source) { - boolean enabled = defaultGroupEl.isOneSelected() && defaultGroupEl.isSelected(0); - forumModule.setPseudonymForGroupEnabledByDefault(enabled); - } else if(defaultCourseEl == source) { - boolean enabled = defaultCourseEl.isOneSelected() && defaultCourseEl.isSelected(0); - forumModule.setPseudonymForCourseEnabledByDefault(enabled); - } - super.formInnerEvent(ureq, source, event); - } - - @Override - protected void formOK(UserRequest ureq) { + protected void event(UserRequest ureq, Component source, Event event) { // } + } diff --git a/src/main/java/org/olat/modules/fo/ui/ForumPseudonymsAdminController.java b/src/main/java/org/olat/modules/fo/ui/ForumPseudonymsAdminController.java new file mode 100644 index 00000000000..fdbf7951916 --- /dev/null +++ b/src/main/java/org/olat/modules/fo/ui/ForumPseudonymsAdminController.java @@ -0,0 +1,187 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.fo.ui; + +import java.util.List; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItem; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.FlexiTableElement; +import org.olat.core.gui.components.form.flexible.elements.FormLink; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; +import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiColumnModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModelFactory; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableSearchEvent; +import org.olat.core.gui.components.form.flexible.impl.elements.table.SelectionEvent; +import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController; +import org.olat.core.gui.control.generic.modal.DialogBoxController; +import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory; +import org.olat.core.util.Util; +import org.olat.modules.fo.Forum; +import org.olat.modules.fo.Pseudonym; +import org.olat.modules.fo.manager.ForumManager; +import org.olat.modules.fo.model.PseudonymStatistics; +import org.olat.modules.fo.ui.ForumPseudonymsDataModel.PseudoCols; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 19 oct. 2016<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class ForumPseudonymsAdminController extends FormBasicController { + + private FormLink newPseudonymButton; + private FlexiTableElement tableEl; + private ForumPseudonymsDataModel model; + + private CloseableModalController cmc; + private NewPseudonymController newPseudoCtrl; + private DialogBoxController confirmDeleteBox; + + @Autowired + private ForumManager forumManager; + + public ForumPseudonymsAdminController(UserRequest ureq, WindowControl wControl) { + super(ureq, wControl, "admin_pseudonyms"); + setTranslator(Util.createPackageTranslator(Forum.class, getLocale(), getTranslator())); + + initForm(ureq); + loadModel(null); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + setFormTitle("admin.pseudonyms.title"); + + newPseudonymButton = uifactory.addFormLink("new.pseudonym", formLayout, Link.BUTTON); + + FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel(); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(PseudoCols.pseudonym)); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(PseudoCols.creationDate)); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(PseudoCols.numOfMessages)); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel("delete", translate("delete"), "delete")); + + model = new ForumPseudonymsDataModel(columnsModel); + tableEl = uifactory.addTableElement(getWindowControl(), "pseudonyms", model, getTranslator(), formLayout); + tableEl.setCustomizeColumns(false); + tableEl.setElementCssClass("o_forum"); + tableEl.setEmtpyTableMessageKey("forum.emtpy"); + tableEl.setSearchEnabled(true); + } + + @Override + protected void doDispose() { + // + } + + private void loadModel(String searchString) { + List<PseudonymStatistics> stats = forumManager.getPseudonymStatistics(searchString); + model.setObjects(stats); + tableEl.reset(true, true, true); + } + + @Override + protected void formOK(UserRequest ureq) { + // + } + + @Override + protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + if(newPseudonymButton == source) { + doNewPseudonym(ureq); + } else if(tableEl == source) { + String cmd = event.getCommand(); + if(event instanceof SelectionEvent) { + SelectionEvent se = (SelectionEvent)event; + if(se.getIndex() >= 0 && se.getIndex() < model.getRowCount()) { + PseudonymStatistics row = model.getObject(se.getIndex()); + if("delete".equals(cmd)) { + doConfirmDelete(ureq, row); + } + } + } else if(event instanceof FlexiTableSearchEvent) { + doSearch((FlexiTableSearchEvent)event); + } + } + super.formInnerEvent(ureq, source, event); + } + + @Override + protected void event(UserRequest ureq, Controller source, Event event) { + if(confirmDeleteBox == source) { + if (DialogBoxUIFactory.isYesEvent(event)) { + doDelete((PseudonymStatistics)confirmDeleteBox.getUserObject()); + loadModel(tableEl.getQuickSearchString()); + } + } else if(newPseudoCtrl == source) { + + cmc.deactivate(); + cleanUp(); + } else if(cmc == source) { + cleanUp(); + } + super.event(ureq, source, event); + } + + private void cleanUp() { + removeAsListenerAndDispose(newPseudoCtrl); + removeAsListenerAndDispose(cmc); + newPseudoCtrl = null; + cmc = null; + } + + protected void doSearch(FlexiTableSearchEvent event) { + loadModel(event.getSearch()); + } + + private void doNewPseudonym(UserRequest ureq) { + if(newPseudoCtrl != null) return; + + newPseudoCtrl = new NewPseudonymController(ureq, getWindowControl()); + listenTo(newPseudoCtrl); + + String title = translate("new.pseudonym"); + cmc = new CloseableModalController(getWindowControl(), "close", newPseudoCtrl.getInitialComponent(), true, title); + listenTo(cmc); + cmc.activate(); + } + + private void doConfirmDelete(UserRequest ureq, PseudonymStatistics row) { + String[] args = new String[] { row.getPseudonym(), row.getNumOfMessages().toString() }; + String title = translate("confirm.delete.pseudonym.title", args); + String msg = translate("confirm.detele.pseudonym.msg", args); + confirmDeleteBox = activateYesNoDialog(ureq, title, msg, confirmDeleteBox); + confirmDeleteBox.setUserObject(row); + } + + private void doDelete(PseudonymStatistics row) { + Pseudonym pseudonym = forumManager.getPseudonymByKey(row.getKey()); + forumManager.deletePseudonym(pseudonym); + } +} diff --git a/src/main/java/org/olat/modules/fo/ui/ForumPseudonymsDataModel.java b/src/main/java/org/olat/modules/fo/ui/ForumPseudonymsDataModel.java new file mode 100644 index 00000000000..03df1381f9a --- /dev/null +++ b/src/main/java/org/olat/modules/fo/ui/ForumPseudonymsDataModel.java @@ -0,0 +1,99 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.fo.ui; + +import java.util.List; + +import org.olat.core.commons.persistence.SortKey; +import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiTableDataModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiSortableColumnDef; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.SortableFlexiTableDataModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.SortableFlexiTableModelDelegate; +import org.olat.modules.fo.model.PseudonymStatistics; + +/** + * + * Initial date: 19 oct. 2016<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class ForumPseudonymsDataModel extends DefaultFlexiTableDataModel<PseudonymStatistics> + implements SortableFlexiTableDataModel<PseudonymStatistics> { + + public ForumPseudonymsDataModel(FlexiTableColumnModel columnModel) { + super(columnModel); + } + + @Override + public void sort(SortKey orderBy) { + SortableFlexiTableModelDelegate<PseudonymStatistics> sorter = new SortableFlexiTableModelDelegate<>(orderBy, this, null); + List<PseudonymStatistics> views = sorter.sort(); + setObjects(views); + } + + @Override + public Object getValueAt(int row, int col) { + PseudonymStatistics stats = getObject(row); + return getValueAt(stats, col); + } + + @Override + public Object getValueAt(PseudonymStatistics row, int col) { + switch(PseudoCols.values()[col]) { + case pseudonym: return row.getPseudonym(); + case creationDate: return row.getCreationDate(); + case numOfMessages: return row.getNumOfMessages(); + default: return "ERROR"; + } + } + + @Override + public ForumPseudonymsDataModel createCopyWithEmptyList() { + return new ForumPseudonymsDataModel(getTableColumnModel()); + } + + public enum PseudoCols implements FlexiSortableColumnDef { + pseudonym("table.header.typeimg"), + creationDate("table.thread"), + numOfMessages("table.userfriendlyname"); + + private final String i18nKey; + + private PseudoCols(String i18nKey) { + this.i18nKey = i18nKey; + } + + @Override + public String i18nHeaderKey() { + return i18nKey; + } + + @Override + public boolean sortable() { + return true; + } + + @Override + public String sortKey() { + return name(); + } + } +} diff --git a/src/main/java/org/olat/modules/fo/ui/ForumSettingsAdminController.java b/src/main/java/org/olat/modules/fo/ui/ForumSettingsAdminController.java new file mode 100644 index 00000000000..d616377767f --- /dev/null +++ b/src/main/java/org/olat/modules/fo/ui/ForumSettingsAdminController.java @@ -0,0 +1,115 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.fo.ui; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItem; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement; +import org.olat.core.gui.components.form.flexible.elements.SingleSelection; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.util.Util; +import org.olat.modules.fo.Forum; +import org.olat.modules.fo.ForumModule; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 10.11.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class ForumSettingsAdminController extends FormBasicController { + + private static final String[] anonymousPostingKeys = new String[]{ "on" }; + private static final String[] defaultKeys = new String[]{ "enabled", "disabled" }; + + private MultipleSelectionElement anonymousPostingEl; + private SingleSelection defaultCourseEl, defaultGroupEl; + + @Autowired + private ForumModule forumModule; + + public ForumSettingsAdminController(UserRequest ureq, WindowControl wControl) { + super(ureq, wControl); + setTranslator(Util.createPackageTranslator(Forum.class, getLocale(), getTranslator())); + + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + setFormTitle("admin.title"); + + String[] anonymousPostingValues = new String[]{ "" }; + anonymousPostingEl = uifactory.addCheckboxesHorizontal("anonymous.posting", "anonymous.posting", formLayout, + anonymousPostingKeys, anonymousPostingValues); + if(forumModule.isAnonymousPostingWithPseudonymEnabled()) { + anonymousPostingEl.select(anonymousPostingKeys[0], true); + } + anonymousPostingEl.addActionListener(FormEvent.ONCHANGE); + + String[] defaultValues = new String[]{ + translate("anonymous.default.enabled"), translate("anonymous.default.disabled") + }; + defaultCourseEl = uifactory.addRadiosHorizontal("anonymous.course.default", "anonymous.course.default", formLayout, + defaultKeys, defaultValues); + if(forumModule.isPseudonymForCourseEnabledByDefault()) { + defaultCourseEl.select(defaultKeys[0], true); + } else { + defaultCourseEl.select(defaultKeys[1], true); + } + defaultCourseEl.addActionListener(FormEvent.ONCHANGE); + + updateUI(); + } + + private void updateUI() { + defaultCourseEl.setVisible(anonymousPostingEl.isAtLeastSelected(1)); + } + + @Override + protected void doDispose() { + // + } + + @Override + protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + if(anonymousPostingEl == source) { + forumModule.setAnonymousPostingWithPseudonymEnabled(anonymousPostingEl.isAtLeastSelected(1)); + updateUI(); + } else if(defaultGroupEl == source) { + boolean enabled = defaultGroupEl.isOneSelected() && defaultGroupEl.isSelected(0); + forumModule.setPseudonymForGroupEnabledByDefault(enabled); + } else if(defaultCourseEl == source) { + boolean enabled = defaultCourseEl.isOneSelected() && defaultCourseEl.isSelected(0); + forumModule.setPseudonymForCourseEnabledByDefault(enabled); + } + super.formInnerEvent(ureq, source, event); + } + + @Override + protected void formOK(UserRequest ureq) { + // + } +} diff --git a/src/main/java/org/olat/modules/fo/ui/MessageEditController.java b/src/main/java/org/olat/modules/fo/ui/MessageEditController.java index bd6f04f6ede..7e98b380e42 100644 --- a/src/main/java/org/olat/modules/fo/ui/MessageEditController.java +++ b/src/main/java/org/olat/modules/fo/ui/MessageEditController.java @@ -72,6 +72,7 @@ import org.olat.modules.fo.ForumCallback; import org.olat.modules.fo.ForumChangedEvent; import org.olat.modules.fo.ForumLoggingAction; import org.olat.modules.fo.Message; +import org.olat.modules.fo.Pseudonym; import org.olat.modules.fo.manager.ForumManager; import org.olat.modules.fo.ui.events.ErrorEditMessage; import org.olat.user.DisplayPortraitController; @@ -102,7 +103,7 @@ public class MessageEditController extends FormBasicController { private static final String[] enableKeys = new String[]{ "on" }; private RichTextElement bodyEl; - private TextElement titleEl, pseudonymEl; + private TextElement titleEl, pseudonymEl, passwordEl; private MultipleSelectionElement usePseudonymEl; private FileElement fileUpload; @@ -201,6 +202,10 @@ public class MessageEditController extends FormBasicController { } pseudonymEl = uifactory.addTextElement("pseudonym", "pseudonym", 128, message.getPseudonym(), formLayout); pseudonymEl.setElementCssClass("o_sel_forum_message_alias"); + + passwordEl = uifactory.addPasswordElement("password", "password", 128, message.getPseudonym(), formLayout); + passwordEl.setElementCssClass("o_sel_forum_message_alias_pass"); + passwordEl.setPlaceholderKey("password.placeholder", null); if(guestOnly) { usePseudonymEl.setVisible(false); @@ -209,6 +214,10 @@ public class MessageEditController extends FormBasicController { proposedPseudonym = (String)ureq.getUserSession().getEntry("FOPseudo" + forum.getKey()); if(StringHelper.containsNonWhitespace(proposedPseudonym)) { pseudonymEl.setValue(proposedPseudonym); + String proposedPassword = (String)ureq.getUserSession().getEntry("FOPseudo-" + proposedPseudonym); + if(StringHelper.containsNonWhitespace(proposedPassword)) { + passwordEl.setValue(proposedPassword); + } } } else if(userIsMsgCreator) { pseudonymEl.setLabel(null, null); @@ -217,13 +226,19 @@ public class MessageEditController extends FormBasicController { if(StringHelper.containsNonWhitespace(proposedPseudonym)) { pseudonymEl.setValue(proposedPseudonym); usePseudonymEl.select(enableKeys[0], true); + String proposedPassword = (String)ureq.getUserSession().getEntry("FOPseudo-" + proposedPseudonym); + if(StringHelper.containsNonWhitespace(proposedPassword)) { + passwordEl.setValue(proposedPassword); + } } usePseudonymEl.setMandatory(usePseudonymEl.isAtLeastSelected(1)); pseudonymEl.setVisible(usePseudonymEl.isAtLeastSelected(1)); + passwordEl.setVisible(usePseudonymEl.isAtLeastSelected(1)); } else { usePseudonymEl.setVisible(false); pseudonymEl.setLabel("use.pseudonym", null); pseudonymEl.setEnabled(false); + passwordEl.setVisible(false); } } @@ -334,13 +349,19 @@ public class MessageEditController extends FormBasicController { boolean allOk = true; if(usePseudonymEl != null) { pseudonymEl.clearError(); + passwordEl.clearError(); if(guestOnly || usePseudonymEl.isAtLeastSelected(1)) { - if(!StringHelper.containsNonWhitespace(pseudonymEl.getValue())) { + String pseudonym = pseudonymEl.getValue(); + String password = passwordEl.getValue(); + + if(!StringHelper.containsNonWhitespace(pseudonym)) { pseudonymEl.setErrorKey("form.legende.mandatory", null); allOk &= false; - } else if(!validatePseudonym(pseudonymEl.getValue())) { + } else if(!validatePseudonym(pseudonym)) { pseudonymEl.setErrorKey("error.pseudonym", null); allOk &= false; + } else if(!validatePseudonymProtected(pseudonym, password)) { + allOk &= false; } } } @@ -361,6 +382,53 @@ public class MessageEditController extends FormBasicController { return allOk; } + + /** + * No password: + * <ul> + * <li>exists pseudonym with password: error</li> + * <li>doesn't exist pseudonym with passwort -> can use the pseudonym</li> + * </ul> + * With password: + * <ul> + * <li>exists pseudonym with password + password wrong: error</li> + * <li>exists pseudonym with password + password ok: ok</li> + * <li>exists pseudonym with password + password wrong: error</li> + * </ul> + * + * @param value + * @param password + * @return + */ + private boolean validatePseudonymProtected(String value, String password) { + boolean allOk = true; + + if(StringHelper.containsNonWhitespace(password)) { + List<Pseudonym> pseudonyms = fm.getPseudonyms(value); + if(pseudonyms.size() > 0) { + boolean authenticated = false; + for(Pseudonym pseudonym:pseudonyms) { + if(fm.authenticatePseudonym(pseudonym, password)) { + authenticated = true; + break; + } + } + + if(!authenticated) { + passwordEl.setErrorKey("error.pseudonym.authentication", null); + allOk &= false; + } + } else if(fm.isPseudonymInUseInForums(value)) { + pseudonymEl.setErrorKey("error.pseudonym", null); + allOk &= false; + } + } else if(fm.isPseudonymProtected(value)) { + pseudonymEl.setErrorKey("error.pseudonym.protected", null); + allOk &= false; + } + + return allOk; + } /** * @see org.olat.core.gui.components.form.flexible.impl.FormBasicController#formOK(org.olat.core.gui.UserRequest) @@ -390,7 +458,31 @@ public class MessageEditController extends FormBasicController { message.setBody(body.trim()); if(usePseudonymEl != null && (usePseudonymEl.isAtLeastSelected(1) || guestOnly)) { - message.setPseudonym(pseudonymEl.getValue()); + String password = passwordEl.getValue(); + String pseudonym = pseudonymEl.getValue(); + if(StringHelper.containsNonWhitespace(password)) { + List<Pseudonym> protectedPseudonyms = fm.getPseudonyms(pseudonym); + if(protectedPseudonyms.isEmpty()) { + fm.createProtectedPseudonym(pseudonym, password); + ureq.getUserSession().putEntry("FOPseudo-" + pseudonym, password); + } else { + //we double check the password + boolean authenticated = false; + for(Pseudonym protectedPseudonym:protectedPseudonyms) { + if(fm.authenticatePseudonym(protectedPseudonym, password)) { + ureq.getUserSession().putEntry("FOPseudo-" + protectedPseudonym.getPseudonym(), password); + authenticated = true; + break; + } + } + + if(!authenticated) { + validateFormLogic(ureq); + return; + } + } + } + message.setPseudonym(pseudonym); if(guestOnly) { ureq.getUserSession().putEntry("FOPseudo" + forum.getKey(), message.getPseudonym()); } @@ -467,6 +559,7 @@ public class MessageEditController extends FormBasicController { if(usePseudonymEl == source) { usePseudonymEl.setMandatory(usePseudonymEl.isAtLeastSelected(1)); pseudonymEl.setVisible(usePseudonymEl.isAtLeastSelected(1)); + passwordEl.setVisible(usePseudonymEl.isAtLeastSelected(1)); } else if (source == fileUpload) { if (fileUpload.isUploadSuccess()) { String fileName = fileUpload.getUploadFileName(); diff --git a/src/main/java/org/olat/modules/fo/ui/NewPseudonymController.java b/src/main/java/org/olat/modules/fo/ui/NewPseudonymController.java new file mode 100644 index 00000000000..5d35894f475 --- /dev/null +++ b/src/main/java/org/olat/modules/fo/ui/NewPseudonymController.java @@ -0,0 +1,134 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.fo.ui; + +import java.util.List; + +import org.olat.basesecurity.BaseSecurity; +import org.olat.basesecurity.IdentityShort; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.TextElement; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.util.StringHelper; +import org.olat.core.util.Util; +import org.olat.modules.fo.Forum; +import org.olat.modules.fo.Pseudonym; +import org.olat.modules.fo.manager.ForumManager; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 19 oct. 2016<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class NewPseudonymController extends FormBasicController { + + private TextElement pseudonymEl, passwordEl; + + @Autowired + private ForumManager forumManager; + @Autowired + private BaseSecurity securityManager; + + public NewPseudonymController(UserRequest ureq, WindowControl wControl) { + super(ureq, wControl); + setTranslator(Util.createPackageTranslator(Forum.class, getLocale(), getTranslator())); + + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + pseudonymEl = uifactory.addTextElement("pseudonym", "new.pseudonym.label", 128, "", formLayout); + pseudonymEl.setElementCssClass("o_sel_forum_alias"); + + passwordEl = uifactory.addPasswordElement("password", "new.password.label", 128, "", formLayout); + passwordEl.setElementCssClass("o_sel_forum_alias_pass"); + + FormLayoutContainer buttonsCont = FormLayoutContainer.createButtonLayout("buttons", getTranslator()); + buttonsCont.setRootForm(mainForm); + formLayout.add(buttonsCont); + uifactory.addFormCancelButton("cancel", buttonsCont, ureq, getWindowControl()); + uifactory.addFormSubmitButton("create.pseudonym", buttonsCont); + } + + @Override + protected void doDispose() { + // + } + + @Override + protected boolean validateFormLogic(UserRequest ureq) { + boolean allOk = true; + + pseudonymEl.clearError(); + passwordEl.clearError(); + String value = pseudonymEl.getValue(); + String password = passwordEl.getValue(); + + if(StringHelper.containsNonWhitespace(value)) { + List<Pseudonym> pseudonyms = forumManager.getPseudonyms(value); + if(pseudonyms.size() > 0) { + pseudonymEl.setErrorKey("error.pseudonym", null); + allOk &= false; + } else if(forumManager.isPseudonymInUseInForums(value)) { + pseudonymEl.setErrorKey("error.pseudonym", null); + allOk &= false; + } + + if(allOk) { + List<IdentityShort> sameValues = securityManager.searchIdentityShort(value, 2); + if(sameValues.size() > 0) { + pseudonymEl.setErrorKey("error.pseudonym", null); + allOk &= false; + } + } + } else { + pseudonymEl.setErrorKey("form.legende.mandatory", null); + allOk &= false; + } + + if(!StringHelper.containsNonWhitespace(password)) { + passwordEl.setErrorKey("form.legende.mandatory", null); + allOk &= false; + } + + return allOk & super.validateFormLogic(ureq); + } + + @Override + protected void formOK(UserRequest ureq) { + String value = pseudonymEl.getValue(); + String password = passwordEl.getValue(); + forumManager.createProtectedPseudonym(value, password); + fireEvent(ureq, Event.DONE_EVENT); + } + + @Override + protected void formCancelled(UserRequest ureq) { + fireEvent(ureq, Event.CANCELLED_EVENT); + } +} diff --git a/src/main/java/org/olat/modules/fo/ui/_content/admin.html b/src/main/java/org/olat/modules/fo/ui/_content/admin.html new file mode 100644 index 00000000000..656511813b7 --- /dev/null +++ b/src/main/java/org/olat/modules/fo/ui/_content/admin.html @@ -0,0 +1,2 @@ +$r.render("settings") +$r.render("pseudonyms") \ No newline at end of file diff --git a/src/main/java/org/olat/modules/fo/ui/_content/admin_pseudonyms.html b/src/main/java/org/olat/modules/fo/ui/_content/admin_pseudonyms.html new file mode 100644 index 00000000000..6cbb770771f --- /dev/null +++ b/src/main/java/org/olat/modules/fo/ui/_content/admin_pseudonyms.html @@ -0,0 +1,8 @@ +<div class="o_form o_block_large clearfix"> + <fieldset> + <legend>$r.translate("admin.pseudonyms.title")</legend> + <div class="o_desc">$r.translate("admin.pseudonyms.descr")</div> + <div class="o_button_group o_button_group_right">$r.render("new.pseudonym")</div> + $r.render("pseudonyms") + </fieldset> +</div> diff --git a/src/main/resources/META-INF/persistence.xml b/src/main/resources/META-INF/persistence.xml index 69904aaf5c8..24688e98db3 100644 --- a/src/main/resources/META-INF/persistence.xml +++ b/src/main/resources/META-INF/persistence.xml @@ -152,6 +152,7 @@ <class>org.olat.modules.fo.model.MessagePeekviewImpl</class> <class>org.olat.modules.fo.model.ReadMessageImpl</class> <class>org.olat.modules.fo.model.MessageStatistics</class> + <class>org.olat.modules.fo.model.PseudonymImpl</class> <class>org.olat.modules.gotomeeting.model.GoToMeetingImpl</class> <class>org.olat.modules.gotomeeting.model.GoToOrganizerImpl</class> <class>org.olat.modules.gotomeeting.model.GoToRegistrantImpl</class> diff --git a/src/main/resources/database/mysql/alter_11_x_0_to_11_1_0.sql b/src/main/resources/database/mysql/alter_11_x_0_to_11_1_0.sql new file mode 100644 index 00000000000..16182d4296a --- /dev/null +++ b/src/main/resources/database/mysql/alter_11_x_0_to_11_1_0.sql @@ -0,0 +1,12 @@ +create table o_forum_pseudonym ( + id bigint not null auto_increment, + creationdate datetime not null, + p_pseudonym varchar(255) not null, + p_credential varchar(255) not null, + p_salt varchar(255) not null, + p_hashalgorithm varchar(16) not null, + primary key (id) +); + +create index forum_pseudonym_idx on o_forum_pseudonym (p_pseudonym); +create index forum_msg_pseudonym_idx on o_message (pseudonym); \ No newline at end of file diff --git a/src/main/resources/database/mysql/setupDatabase.sql b/src/main/resources/database/mysql/setupDatabase.sql index 8434608326d..80c88306b43 100644 --- a/src/main/resources/database/mysql/setupDatabase.sql +++ b/src/main/resources/database/mysql/setupDatabase.sql @@ -6,6 +6,15 @@ create table if not exists o_forum ( creationdate datetime, primary key (forum_id) ); +create table o_forum_pseudonym ( + id bigint not null auto_increment, + creationdate datetime not null, + p_pseudonym varchar(255) not null, + p_credential varchar(255) not null, + p_salt varchar(255) not null, + p_hashalgorithm varchar(16) not null, + primary key (id) +); create table if not exists o_property ( id bigint not null, version mediumint unsigned not null, @@ -2043,6 +2052,7 @@ alter table oc_lock add index FK9E30F4B66115906D (identity_fk), add constraint F alter table hibernate_unique_key ENGINE = InnoDB; alter table o_forum ENGINE = InnoDB; +alter table o_forum_pseudonym ENGINE = InnoDB; alter table o_property ENGINE = InnoDB; alter table o_bs_secgroup ENGINE = InnoDB; alter table o_bs_group ENGINE = InnoDB; @@ -2367,10 +2377,13 @@ alter table o_message add constraint FKF26C837A3FBEB83 foreign key (modifier_id) alter table o_message add constraint FKF26C8377B66B0D0 foreign key (parent_id) references o_message (message_id); alter table o_message add constraint FKF26C8378EAC1DBB foreign key (topthread_id) references o_message (message_id); alter table o_message add constraint FKF26C8371CB7C4A3 foreign key (forum_fk) references o_forum (forum_id); +create index forum_msg_pseudonym_idx on o_message (pseudonym); create index readmessage_forum_idx on o_readmessage (forum_id); create index readmessage_identity_idx on o_readmessage (identity_id); +create index forum_pseudonym_idx on o_forum_pseudonym (p_pseudonym); + -- project broker create index projectbroker_project_broker_idx on o_projectbroker_project (projectbroker_fk); create index projectbroker_project_id_idx on o_projectbroker_project (project_id); diff --git a/src/main/resources/database/oracle/alter_11_x_0_to_11_1_0.sql b/src/main/resources/database/oracle/alter_11_x_0_to_11_1_0.sql new file mode 100644 index 00000000000..33be98a0bb6 --- /dev/null +++ b/src/main/resources/database/oracle/alter_11_x_0_to_11_1_0.sql @@ -0,0 +1,12 @@ +create table o_forum_pseudonym ( + id number(20) GENERATED ALWAYS AS IDENTITY, + creationdate date not null, + p_pseudonym varchar2(255 char) not null, + p_credential varchar2(255 char) not null, + p_salt varchar2(255 char) not null, + p_hashalgorithm varchar2(16 char) not null, + primary key (id) +); + +create index forum_pseudonym_idx on o_forum_pseudonym (p_pseudonym); +create index forum_msg_pseudonym_idx on o_message (pseudonym); diff --git a/src/main/resources/database/oracle/setupDatabase.sql b/src/main/resources/database/oracle/setupDatabase.sql index 95379a5194a..b9f88540b0b 100644 --- a/src/main/resources/database/oracle/setupDatabase.sql +++ b/src/main/resources/database/oracle/setupDatabase.sql @@ -7,7 +7,15 @@ CREATE TABLE o_forum ( creationdate date, PRIMARY KEY (forum_id) ); - +CREATE TABLE o_forum_pseudonym ( + id number(20) GENERATED ALWAYS AS IDENTITY, + creationdate date not null, + p_pseudonym varchar2(255 char) NOT NULL, + p_credential varchar2(255 char) NOT NULL, + p_salt varchar2(255 char) NOT NULL, + p_hashalgorithm varchar2(16 char) NOT NULL, + PRIMARY KEY (id) +); CREATE TABLE o_property ( id number(20) NOT NULL, @@ -2465,10 +2473,13 @@ alter table o_message add constraint FKF26C8378EAC1DBB foreign key (topthread_id create index FKF26C8378EAC1DBB on o_message (topthread_id); alter table o_message add constraint FKF26C8371CB7C4A3 foreign key (forum_fk) references o_forum (forum_id); create index FKF26C8371CB7C4A3 on o_message (forum_fk); +create index forum_pseudonym_idx on o_forum_pseudonym (p_pseudonym); create index readmessage_forum_idx on o_readmessage (forum_id); create index readmessage_identity_idx on o_readmessage (identity_id); +create index forum_msg_pseudonym_idx on o_message (pseudonym); + -- project broker create index projectbroker_prj_broker_idx on o_projectbroker_project (projectbroker_fk); -- index projectbroker_project_id_idx created by unique constraint diff --git a/src/main/resources/database/postgresql/alter_11_x_0_to_11_1_0.sql b/src/main/resources/database/postgresql/alter_11_x_0_to_11_1_0.sql new file mode 100644 index 00000000000..3fb290f2dc0 --- /dev/null +++ b/src/main/resources/database/postgresql/alter_11_x_0_to_11_1_0.sql @@ -0,0 +1,12 @@ +create table o_forum_pseudonym ( + id bigserial, + creationdate timestamp not null, + p_pseudonym varchar(255) not null, + p_credential varchar(255) not null, + p_salt varchar(255) not null, + p_hashalgorithm varchar(16) not null, + primary key (id) +); + +create index forum_pseudonym_idx on o_forum_pseudonym (p_pseudonym); +create index forum_msg_pseudonym_idx on o_message (pseudonym); \ No newline at end of file diff --git a/src/main/resources/database/postgresql/setupDatabase.sql b/src/main/resources/database/postgresql/setupDatabase.sql index 0baa8040cf5..c1c6e867e6d 100644 --- a/src/main/resources/database/postgresql/setupDatabase.sql +++ b/src/main/resources/database/postgresql/setupDatabase.sql @@ -4,6 +4,15 @@ create table o_forum ( creationdate timestamp, primary key (forum_id) ); +create table o_forum_pseudonym ( + id bigserial, + creationdate timestamp not null, + p_pseudonym varchar(255) not null, + p_credential varchar(255) not null, + p_salt varchar(255) not null, + p_hashalgorithm varchar(16) not null, + primary key (id) +); create table o_property ( id int8 not null, version int4 not null, @@ -2318,10 +2327,13 @@ alter table o_message add constraint FKF26C8378EAC1DBB foreign key (topthread_id create index idx_message_top_idx on o_message (topthread_id); alter table o_message add constraint FKF26C8371CB7C4A3 foreign key (forum_fk) references o_forum; create index idx_message_forum_idx on o_message (forum_fk); +create index forum_msg_pseudonym_idx on o_message (pseudonym); create index readmessage_forum_idx on o_readmessage (forum_id); create index readmessage_identity_idx on o_readmessage (identity_id); +create index forum_pseudonym_idx on o_forum_pseudonym (p_pseudonym); + -- project broker create index projectbroker_project_broker_idx on o_projectbroker_project (projectbroker_fk); -- index created on projectbroker_project_id_idx unique constraint diff --git a/src/test/java/org/olat/modules/fo/ForumManagerTest.java b/src/test/java/org/olat/modules/fo/ForumManagerTest.java index dfb4a18ae0a..0c5e0bb8fc4 100644 --- a/src/test/java/org/olat/modules/fo/ForumManagerTest.java +++ b/src/test/java/org/olat/modules/fo/ForumManagerTest.java @@ -43,6 +43,7 @@ import org.olat.modules.fo.manager.ForumManager; import org.olat.modules.fo.model.ForumThread; import org.olat.modules.fo.model.ForumUserStatistics; import org.olat.modules.fo.model.MessageImpl; +import org.olat.modules.fo.model.PseudonymStatistics; import org.olat.modules.fo.ui.MessagePeekview; import org.olat.test.JunitTestHelper; import org.olat.test.OlatTestCase; @@ -1121,4 +1122,186 @@ public class ForumManagerTest extends OlatTestCase { Assert.assertEquals(message.getForum(), masterForum); } } + + @Test + public void createProtectedPseudonym() { + String pseudonym = UUID.randomUUID().toString(); + String password = "secret"; + + Pseudonym protectedPseudo = forumManager.createProtectedPseudonym(pseudonym, password); + dbInstance.commit(); + + Assert.assertNotNull(protectedPseudo); + Assert.assertNotNull(protectedPseudo.getKey()); + Assert.assertNotNull(protectedPseudo.getCreationDate()); + Assert.assertNotNull(protectedPseudo.getCredential()); + Assert.assertNotNull(protectedPseudo.getSalt()); + Assert.assertNotNull(protectedPseudo.getAlgorithm()); + Assert.assertEquals(pseudonym, protectedPseudo.getPseudonym()); + } + + @Test + public void loadProtectedPseudonym() { + String pseudonym = UUID.randomUUID().toString(); + String password = "secret"; + + Pseudonym protectedPseudo = forumManager.createProtectedPseudonym(pseudonym, password); + dbInstance.commitAndCloseSession(); + + //load and check the content + Pseudonym reloadedPseudo = forumManager.getPseudonymByKey(protectedPseudo.getKey()); + Assert.assertNotNull(reloadedPseudo); + Assert.assertNotNull(reloadedPseudo.getKey()); + Assert.assertNotNull(reloadedPseudo.getCreationDate()); + Assert.assertNotNull(reloadedPseudo.getCredential()); + Assert.assertNotNull(reloadedPseudo.getSalt()); + Assert.assertNotNull(reloadedPseudo.getAlgorithm()); + Assert.assertEquals(pseudonym, reloadedPseudo.getPseudonym()); + } + + @Test + public void getPseudonyms() { + String pseudonym = UUID.randomUUID().toString(); + String password = "secret"; + + Pseudonym protectedPseudo = forumManager.createProtectedPseudonym(pseudonym, password); + dbInstance.commitAndCloseSession(); + + //load and check the content + List<Pseudonym> thePseudo = forumManager.getPseudonyms(pseudonym); + Assert.assertNotNull(thePseudo); + Assert.assertEquals(1, thePseudo.size()); + Assert.assertTrue(thePseudo.contains(protectedPseudo)); + + //negative tests + List<Pseudonym> noPseudo = forumManager.getPseudonyms(UUID.randomUUID().toString() + "break"); + Assert.assertNotNull(noPseudo); + Assert.assertTrue(noPseudo.isEmpty()); + } + + @Test + public void authenticatePseudonym() { + String pseudonym = UUID.randomUUID().toString(); + String password = "thesecret"; + forumManager.createProtectedPseudonym(pseudonym, password); + dbInstance.commitAndCloseSession(); + + //load + List<Pseudonym> thePseudos = forumManager.getPseudonyms(pseudonym); + Assert.assertNotNull(thePseudos); + Assert.assertEquals(1, thePseudos.size()); + Pseudonym thePseudo = thePseudos.get(0); + + //check authentication + boolean ok = forumManager.authenticatePseudonym(thePseudo, password); + Assert.assertTrue(ok); + //negative tests + boolean notOk = forumManager.authenticatePseudonym(thePseudo, "12345"); + Assert.assertFalse(notOk); + } + + @Test + public void isPseudonymProtected() { + //create a reference + String pseudonym = UUID.randomUUID().toString(); + String password = "thesecret"; + forumManager.createProtectedPseudonym(pseudonym, password); + dbInstance.commitAndCloseSession(); + + //load + boolean protectedYes = forumManager.isPseudonymProtected(pseudonym); + Assert.assertTrue(protectedYes); + //negative tests + boolean protectedNo = forumManager.isPseudonymProtected("12345"); + Assert.assertFalse(protectedNo); + } + + @Test + public void isPseudonymInUseInForums() { + Identity id1 = JunitTestHelper.createAndPersistIdentityAsRndUser("fo-1"); + Forum forum = forumManager.addAForum(); + dbInstance.commit(); + + Message thread = forumManager.createMessage(forum, id1, false); + thread.setTitle("Get pseudonym"); + thread.setBody("Get pseudonym"); + String pseudo = "Id pseudo " + UUID.randomUUID(); + thread.setPseudonym(pseudo); + forumManager.addTopMessage(thread); + dbInstance.commit(); + + //load + boolean protectedYes = forumManager.isPseudonymInUseInForums(pseudo); + Assert.assertTrue(protectedYes); + //negative tests + boolean protectedNo = forumManager.isPseudonymInUseInForums("12345"); + Assert.assertFalse(protectedNo); + } + + @Test + public void getPseudonymStatistics() { + Identity id = JunitTestHelper.createAndPersistIdentityAsRndUser("fo-9"); + Forum forum = forumManager.addAForum(); + dbInstance.commit(); + + Message thread = forumManager.createMessage(forum, id, false); + thread.setTitle("Message with pseudonym"); + thread.setBody("Message with pseudonym"); + String pseudo = "Id pseudo " + UUID.randomUUID(); + thread.setPseudonym(pseudo); + forumManager.addTopMessage(thread); + Pseudonym protectedPseudo = forumManager.createProtectedPseudonym(pseudo, "secret"); + dbInstance.commit(); + + //check statistics + List<PseudonymStatistics> stats = forumManager.getPseudonymStatistics(null); + Assert.assertNotNull(stats); + Assert.assertTrue(stats.size() > 0); + PseudonymStatistics thePseudoStats = null; + for(PseudonymStatistics stat:stats) { + if(pseudo.equals(stat.getPseudonym())) { + thePseudoStats = stat; + } + } + Assert.assertNotNull(thePseudoStats); + Assert.assertEquals(protectedPseudo.getKey(), thePseudoStats.getKey()); + Assert.assertEquals(new Long(1l), thePseudoStats.getNumOfMessages()); + + //check with search + } + + @Test + public void getPseudonymStatistics_searchString() { + Identity id = JunitTestHelper.createAndPersistIdentityAsRndUser("fo-10"); + Forum forum = forumManager.addAForum(); + dbInstance.commit(); + + Message thread = forumManager.createMessage(forum, id, false); + thread.setTitle("Message with pseudonym"); + thread.setBody("Message with pseudonym"); + String pseudo = "Search string pseudo " + UUID.randomUUID(); + thread.setPseudonym(pseudo); + forumManager.addTopMessage(thread); + Pseudonym protectedPseudo = forumManager.createProtectedPseudonym(pseudo, "secret"); + dbInstance.commit(); + + //check statistics with search + List<PseudonymStatistics> stats = forumManager.getPseudonymStatistics("Search string"); + Assert.assertNotNull(stats); + Assert.assertTrue(stats.size() > 0); + PseudonymStatistics thePseudoStats = null; + for(PseudonymStatistics stat:stats) { + if(pseudo.equals(stat.getPseudonym())) { + thePseudoStats = stat; + } + } + Assert.assertNotNull(thePseudoStats); + Assert.assertEquals(protectedPseudo.getKey(), thePseudoStats.getKey()); + Assert.assertEquals(new Long(1l), thePseudoStats.getNumOfMessages()); + + //check negative + List<PseudonymStatistics> emptyStats = forumManager.getPseudonymStatistics("This string is never a pseudo"); + Assert.assertNotNull(emptyStats); + Assert.assertTrue(emptyStats.isEmpty()); + } } -- GitLab