From 6abee6e97733216fcd5e0f0a69e1f29d748ef10f Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Fri, 22 Apr 2022 18:20:03 +0200 Subject: [PATCH] implemented editing of list credentials for admin/owner, fixed bug: if login fails due to wrong auth, it is no longer repeated. instead, the list is disabeld --- pom.xml | 2 +- .../srsoftware/widerhall/data/Database.java | 15 ++- .../srsoftware/widerhall/data/ListMember.java | 4 + .../widerhall/data/MailingList.java | 77 ++++++++++--- .../srsoftware/widerhall/mail/ImapClient.java | 5 +- .../widerhall/mail/ProblemListener.java | 7 ++ .../java/de/srsoftware/widerhall/web/Web.java | 107 ++++++++++++++++-- static/templates/add_list.st | 62 +--------- static/templates/css.st | 2 +- static/templates/edit_list.st | 17 +++ static/templates/inspect.st | 1 + static/templates/js.st | 6 +- static/templates/list_data_form.st | 59 ++++++++++ static/templates/listadminlist.st | 3 +- static/templates/navigation.st | 6 +- 15 files changed, 276 insertions(+), 97 deletions(-) create mode 100644 src/main/java/de/srsoftware/widerhall/mail/ProblemListener.java create mode 100644 static/templates/edit_list.st create mode 100644 static/templates/list_data_form.st diff --git a/pom.xml b/pom.xml index c2a2917..396ef35 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.example Widerhall - 0.2.21 + 0.2.22 diff --git a/src/main/java/de/srsoftware/widerhall/data/Database.java b/src/main/java/de/srsoftware/widerhall/data/Database.java index c7af879..b6c3691 100644 --- a/src/main/java/de/srsoftware/widerhall/data/Database.java +++ b/src/main/java/de/srsoftware/widerhall/data/Database.java @@ -183,6 +183,11 @@ public class Database { } } + + public void run() throws SQLException { + compile().run(); + } + /** * set single value for a certain key * @param key @@ -194,6 +199,11 @@ public class Database { return this; } + public Request sort(String field) { + sortFields.add(field); + return this; + } + /** * get the current (i.e. non-final) sql * @return @@ -251,11 +261,6 @@ public class Database { list.add(value); return this; } - - public Request sort(String field) { - sortFields.add(field); - return this; - } } public Database(Connection connection) { diff --git a/src/main/java/de/srsoftware/widerhall/data/ListMember.java b/src/main/java/de/srsoftware/widerhall/data/ListMember.java index b0c5554..f17f8b7 100644 --- a/src/main/java/de/srsoftware/widerhall/data/ListMember.java +++ b/src/main/java/de/srsoftware/widerhall/data/ListMember.java @@ -406,6 +406,10 @@ public class ListMember { } } + public static void updateListEmail(String oldEmail, String newEmail) throws SQLException { + Database.open().update(TABLE_NAME).set(LIST_EMAIL,newEmail).where(LIST_EMAIL,oldEmail).compile().run(); + } + public User user(){ return user; } diff --git a/src/main/java/de/srsoftware/widerhall/data/MailingList.java b/src/main/java/de/srsoftware/widerhall/data/MailingList.java index 15a2eb4..ada0285 100644 --- a/src/main/java/de/srsoftware/widerhall/data/MailingList.java +++ b/src/main/java/de/srsoftware/widerhall/data/MailingList.java @@ -3,14 +3,12 @@ package de.srsoftware.widerhall.data; import de.srsoftware.widerhall.Configuration; import de.srsoftware.widerhall.mail.ImapClient; import de.srsoftware.widerhall.mail.MessageHandler; +import de.srsoftware.widerhall.mail.ProblemListener; import de.srsoftware.widerhall.mail.SmtpClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.mail.Address; -import javax.mail.Flags; -import javax.mail.Message; -import javax.mail.MessagingException; +import javax.mail.*; import javax.mail.internet.AddressException; import javax.mail.internet.InternetAddress; import java.io.*; @@ -27,7 +25,7 @@ import static de.srsoftware.widerhall.data.User.PERMISSION_ADMIN; /** * this class encapsulates a MailingList db object */ -public class MailingList implements MessageHandler { +public class MailingList implements MessageHandler, ProblemListener { public static final String KEY_FORWARD_FROM = "forward_from"; public static final String KEY_FORWARD_ATTACHED = "forward_attached"; public static final String KEY_HIDE_RECEIVERS = "hide_receivers"; @@ -61,11 +59,11 @@ public class MailingList implements MessageHandler { private static final int HIDDEN = 0; private static final int DEFAULT_STATE = STATE_PENDING|STATE_HIDE_RECEIVERS|STATE_PUBLIC_ARCHIVE; private static final String RETAINED_FOLDER = "retained"; - private final String name; - private final String email; + private static final String LAST_ERROR = "last_error"; + private String email, name, lastError; private int state; - private final SmtpClient smtp; - private final ImapClient imap; + private SmtpClient smtp; + private ImapClient imap; private static final HashMap cache = new HashMap<>(); @@ -88,7 +86,7 @@ public class MailingList implements MessageHandler { this.name = name; this.state = state; this.smtp = new SmtpClient(smtpHost,smtpPort,smtpUser,smtpPass); - this.imap = new ImapClient(imapHost,imapPort,imapUser,imapPass,inbox); + this.imap = new ImapClient(imapHost,imapPort,imapUser,imapPass,inbox,this); } public MailingList archive(boolean enabled) throws SQLException { @@ -133,7 +131,8 @@ public class MailingList implements MessageHandler { .append(SMTP_PORT).append(" ").append(INT).append(", ") .append(SMTP_USER).append(" ").append(VARCHAR).append(", ") .append(SMTP_PASS).append(" ").append(VARCHAR).append(", ") - .append(STATE).append(" ").append(INT) + .append(STATE).append(" ").append(INT).append(", ") + .append(LAST_ERROR).append(" ").append(TEXT) .append(");"); Database.open().query(sql).compile().run(); } @@ -146,7 +145,10 @@ public class MailingList implements MessageHandler { public MailingList enable(boolean enable) throws SQLException { if (!enable) imap.stop(); setFlag(STATE_ENABLED,enable); - if (enable) imap.start().addListener(this); + if (enable) { + setLastError(null); + imap.start().addListener(this); + } return this; } @@ -273,12 +275,15 @@ public class MailingList implements MessageHandler { public static MailingList load(String listEmail) { if (listEmail == null) return null; var ml = cache.get(listEmail); - if (ml == null) try { + try { var rs = Database.open() .select(TABLE_NAME) .where(EMAIL,listEmail) .compile().exec(); - if (rs.next()) ml = MailingList.from(rs); + if (rs.next()) { + if (ml == null ) ml = MailingList.from(rs); + ml.lastError = rs.getString(LAST_ERROR); + } } catch (SQLException e) { LOG.debug("Failed to load MailingList: ",e); } @@ -352,6 +357,7 @@ public class MailingList implements MessageHandler { map.put(EMAIL,Map.of(PREFIX,parts[0],DOMAIN,parts[1])); map.put(NAME, name); map.put(STATE, stateMap()); + map.put(LAST_ERROR, lastError); return map; } @@ -381,6 +387,16 @@ public class MailingList implements MessageHandler { return name; } + @Override + public void onImapException(MessagingException e) { + try { + if (e instanceof AuthenticationFailedException afe) this.enable(false); + setLastError(e.getMessage()); + } catch (SQLException sqle) { + LOG.warn("Database error during onImapException ({})",e.getMessage(),sqle); + } + } + @Override public void onMessageReceived(Message message) throws MessagingException { LOG.info("Message received: {}",message.getFrom()); @@ -477,6 +493,7 @@ public class MailingList implements MessageHandler { if (smtp.port() != 0) map.put(SMTP_PORT, smtp.port()); if (smtp.username() != null) map.put(SMTP_USER, smtp.username()); map.put(STATE,stateMap()); + if (lastError != null) map.put(LAST_ERROR, lastError); return map; } @@ -550,6 +567,10 @@ public class MailingList implements MessageHandler { return this; } + private void setLastError(String message) throws SQLException { + Database.open().update(TABLE_NAME).set(LAST_ERROR,message).where(EMAIL,email()).run(); + } + public Map stateMap(){ var map = new HashMap(); if (hasState(STATE_ENABLED)) map.put("enabled",VISIBLE); @@ -664,4 +685,32 @@ public class MailingList implements MessageHandler { return new ArrayList().stream(); } } + + public void update(String name, String email, String imapHost, Integer imapPort, String imapUser, String imapPass, String inbox, String smtpHost, Integer smtpPort, String smtpUser, String smtpPass) throws SQLException { + imap.stop(); + Database.open() + .update(TABLE_NAME) + .set(EMAIL, email) + .set(NAME, name) + .set(IMAP_HOST, imapHost) + .set(IMAP_PORT, imapPort) + .set(IMAP_USER, imapUser) + .set(IMAP_PASS, imapPass) + .set(INBOX,inbox) + .set(SMTP_HOST, smtpHost) + .set(SMTP_PORT, smtpPort) + .set(SMTP_USER, smtpUser) + .set(SMTP_PASS, smtpPass) + .where(EMAIL,email()) + .compile() + .run(); + if (!this.email.equals(email)){ + ListMember.updateListEmail(this.email,email); + this.email = email; + } + this.name = name; + smtp = new SmtpClient(smtpHost,smtpPort,smtpUser,smtpPass); + imap = new ImapClient(imapHost,imapPort,imapUser,imapPass,inbox,this); + if (this.hasState(STATE_ENABLED)) imap.start().addListener(this); + } } diff --git a/src/main/java/de/srsoftware/widerhall/mail/ImapClient.java b/src/main/java/de/srsoftware/widerhall/mail/ImapClient.java index bb364fd..4fe955f 100644 --- a/src/main/java/de/srsoftware/widerhall/mail/ImapClient.java +++ b/src/main/java/de/srsoftware/widerhall/mail/ImapClient.java @@ -14,6 +14,7 @@ public class ImapClient { private static final Logger LOG = LoggerFactory.getLogger(ImapClient.class); private final int port; private final String host, username, password, folderName; + private final ProblemListener problemListener; private IMAPFolder inbox; private ListeningThread listeningThread; private Heartbeat heartbeat; @@ -47,6 +48,7 @@ public class ImapClient { openInbox(); } catch (MessagingException e){ LOG.warn("Connection problem:",e); + problemListener.onImapException(e); } } } @@ -127,12 +129,13 @@ public class ImapClient { } } - public ImapClient(String host, int port, String username, String password, String folderName) { + public ImapClient(String host, int port, String username, String password, String folderName,ProblemListener listener) { this.host = host; this.port = port; this.username = username; this.password = password; this.folderName = folderName; + this.problemListener = listener; } public ImapClient addListener(MessageHandler messageHandler) { diff --git a/src/main/java/de/srsoftware/widerhall/mail/ProblemListener.java b/src/main/java/de/srsoftware/widerhall/mail/ProblemListener.java new file mode 100644 index 0000000..b22d747 --- /dev/null +++ b/src/main/java/de/srsoftware/widerhall/mail/ProblemListener.java @@ -0,0 +1,7 @@ +package de.srsoftware.widerhall.mail; + +import javax.mail.MessagingException; + +public interface ProblemListener { + public void onImapException(MessagingException e); +} diff --git a/src/main/java/de/srsoftware/widerhall/web/Web.java b/src/main/java/de/srsoftware/widerhall/web/Web.java index ffac30f..b46cd29 100644 --- a/src/main/java/de/srsoftware/widerhall/web/Web.java +++ b/src/main/java/de/srsoftware/widerhall/web/Web.java @@ -21,6 +21,7 @@ import java.util.Map; import static de.srsoftware.widerhall.Constants.*; import static de.srsoftware.widerhall.Util.t; import static de.srsoftware.widerhall.data.MailingList.*; +import static de.srsoftware.widerhall.data.User.PERMISSION_ADMIN; public class Web extends TemplateServlet { public static final String WEB_ROOT = "/web"; @@ -29,6 +30,7 @@ public class Web extends TemplateServlet { private static final String CSS = "css"; private static final Logger LOG = LoggerFactory.getLogger(Web.class); private static final String ADMIN = "admin"; + private static final String EDIT_LIST = "edit_list"; private static final String INSPECT = "inspect"; private static final String LOGIN = "login"; private static final String LOGOUT = "logout"; @@ -48,13 +50,10 @@ public class Web extends TemplateServlet { private static final int PRIMARY_KEY_CONSTRAINT = 19; private String addList(HttpServletRequest req, HttpServletResponse resp) { - - var o = req.getSession().getAttribute(USER); - if (!(o instanceof User user)) { - return redirectTo(LOGIN,resp); - } + var user = Util.getUser(req); + if (user == null) return redirectTo(LOGIN,resp); var data = new HashMap(); - data.put(USER, user); + data.put(USER, user.safeMap()); if (!user.hashPermission(User.PERMISSION_CREATE_LISTS)){ data.put(ERROR,t("You are not allowed to create new mailing lists!")); @@ -128,7 +127,7 @@ public class Web extends TemplateServlet { try { var list = MailingList.create(email, name, imapHost, imapPort, imapUser, imapPass, inbox, smtpHost, smtpPort, smtpUser, smtpPass); ListMember.create(list, user, ListMember.STATE_OWNER); - return redirectTo(INDEX, resp); + return redirectTo(ADMIN, resp); } catch (SQLException e) { return t("Failed to create list '{}': {}", name, e.getMessage()); } @@ -164,6 +163,91 @@ public class Web extends TemplateServlet { if (error != null) resp.sendError(400,error); } + public String editList(HttpServletRequest req, HttpServletResponse resp) { + var user = Util.getUser(req); + if (user == null) return redirectTo(LOGIN,resp); + + var data = new HashMap(); + data.put(USER,user.safeMap()); + + var list = Util.getMailingList(req); + data.put(LIST,list.safeMap()); + try { + var allowed = user.hashPermission(PERMISSION_ADMIN) || ListMember.load(list,user).isOwner(); + if (!allowed) return loadTemplate(ADMIN,data,resp); + + var name = req.getParameter(NAME); + data.put(NAME, name); + + var email = req.getParameter(EMAIL); + data.put(EMAIL, email); + + var imapHost = req.getParameter(IMAP_HOST); + data.put(IMAP_HOST, imapHost); + var imapUser = req.getParameter(IMAP_USER); + data.put(IMAP_USER, imapUser); + var imapPass = req.getParameter(IMAP_PASS); + var inbox = req.getParameter(INBOX); + if (inbox == null || inbox.isBlank()) inbox = DEFAULT_INBOX; + data.put(INBOX, inbox); + + var smtpHost = req.getParameter(SMTP_HOST); + data.put(SMTP_HOST, smtpHost); + var smtpUser = req.getParameter(SMTP_USER); + data.put(SMTP_USER, smtpUser); + var smtpPass = req.getParameter(SMTP_PASS); + + Integer imapPort = 993; + data.put(IMAP_PORT, imapPort); + + Integer smtpPort = 465; + data.put(SMTP_PORT, smtpPort); + + if (name == null || name.isBlank() || email == null || email.isBlank()) { + data.put(ERROR, "List name and address are required!"); + return loadTemplate(EDIT_LIST, data, resp); + } + + if (!Util.isEmail(email)) { + data.put(ERROR, t("List email ({}) is not a valid email address!", email)); + return loadTemplate(EDIT_LIST, data, resp); + } + + if (imapHost == null || imapHost.isBlank() || imapUser == null || imapUser.isBlank() || imapPass == null || imapPass.isBlank()) { + data.put(ERROR, "IMAP credentials are required!"); + return loadTemplate(EDIT_LIST, data, resp); + } + + + try { + imapPort = Integer.parseInt(req.getParameter(IMAP_PORT)); + data.put(IMAP_PORT, imapPort); + } catch (NumberFormatException nfe) { + data.put(ERROR, t("'{}' is not a proper port number!", req.getParameter(IMAP_PORT))); + return loadTemplate(EDIT_LIST, data, resp); + } + + if (smtpHost == null || smtpHost.isBlank() || smtpUser == null || smtpUser.isBlank() || smtpPass == null || smtpPass.isBlank()) { + data.put(ERROR, "SMTP credentials are required!"); + return loadTemplate(EDIT_LIST, data, resp); + } + + try { + smtpPort = Integer.parseInt(req.getParameter(SMTP_PORT)); + data.put(SMTP_PORT, smtpPort); + } catch (NumberFormatException nfe) { + data.put(ERROR, t("'{}' is not a proper port number!", req.getParameter(SMTP_PORT))); + return loadTemplate(EDIT_LIST, data, resp); + } + + list.update(name,email,imapHost,imapPort,imapUser,imapPass,inbox,smtpHost,smtpPort,smtpUser,smtpPass); + return loadTemplate(ADMIN,data,resp); + } catch (SQLException e) { + LOG.warn("Editing list {} by {} failed",list.email(),user.email(),e); + return t("Editing list {} by {} failed",list.email(),user.email()); + } + } + private SQLException getCausingException(SQLException sqle) { Throwable cause = sqle.getCause(); while (cause instanceof SQLException){ @@ -230,7 +314,10 @@ public class Web extends TemplateServlet { if (user != null){ if (list != null) data.put(LIST,req.getParameter(LIST)); - //data.put(NOTES,notes); + switch (path){ + case EDIT_LIST: + return editList(req,resp); + } return loadTemplate(path,data,resp); } return redirectTo(LOGIN,resp); @@ -263,6 +350,8 @@ public class Web extends TemplateServlet { switch (path){ case ADD_LIST: return addList(req,resp); + case EDIT_LIST: + return editList(req,resp); case INSPECT: return inspect(req,resp); case LOGIN: @@ -367,7 +456,7 @@ public class Web extends TemplateServlet { try { var user = User.create(email, name, pass); - if (firstUser) user.addPermission(User.PERMISSION_ADMIN|User.PERMISSION_CREATE_LISTS); + if (firstUser) user.addPermission(PERMISSION_ADMIN|User.PERMISSION_CREATE_LISTS); req.getSession().setAttribute("user",user); return redirectTo(INDEX,resp); } catch (SQLException e) { diff --git a/static/templates/add_list.st b/static/templates/add_list.st index 41a4a3e..b09b30e 100644 --- a/static/templates/add_list.st +++ b/static/templates/add_list.st @@ -9,66 +9,8 @@ «navigation()» «userinfo()» -

Widerhall List Creation

«messages()» -
-
- List configuration -
- Basic data - - -
-
- IMAP protocol - - - - - -
-
- SMTP protocol - - - - -
- -
-
+

Widerhall List Creation

+ «list_data_form()» \ No newline at end of file diff --git a/static/templates/css.st b/static/templates/css.st index 2fce79b..f2f2e24 100644 --- a/static/templates/css.st +++ b/static/templates/css.st @@ -3,7 +3,7 @@ label { margin: 5px 0; } -#add_list form, +form#list_data, #login form, #register form, #subscribe form{ diff --git a/static/templates/edit_list.st b/static/templates/edit_list.st new file mode 100644 index 0000000..24724d9 --- /dev/null +++ b/static/templates/edit_list.st @@ -0,0 +1,17 @@ + + + + + + + + + + «navigation()» + «userinfo()» + «messages()» +

Widerhall List Setup

+ «list_data_form()» + «footer()» + + \ No newline at end of file diff --git a/static/templates/inspect.st b/static/templates/inspect.st index eba9b7a..301e47e 100644 --- a/static/templates/inspect.st +++ b/static/templates/inspect.st @@ -14,6 +14,7 @@
Settings + This page shows configuration options for the mailing list. To edit login credentials, goto edit list
Forward permissions

diff --git a/static/templates/js.st b/static/templates/js.st index 6965538..35a1f62 100644 --- a/static/templates/js.st +++ b/static/templates/js.st @@ -110,7 +110,9 @@ function showListOfModeratedLists(data){ if (list.state[state] > 0) states.push(state); } $('').text(states.toString()).appendTo(row); - + if (list.last_error){ + $('').html(''+list.last_error+'').appendTo(row); + } else $('').text('-').appendTo(row); let select = $(' + List name + + +

+
+ IMAP protocol + + + + + +
+
+ SMTP protocol + + + + +
+ +
+
\ No newline at end of file diff --git a/static/templates/listadminlist.st b/static/templates/listadminlist.st index e9bb183..e28209a 100644 --- a/static/templates/listadminlist.st +++ b/static/templates/listadminlist.st @@ -10,14 +10,13 @@ Name Address State + Last error Actions Host Port - User Inbox Host Port - User Add new mailing list diff --git a/static/templates/navigation.st b/static/templates/navigation.st index 3718c00..d7e0725 100644 --- a/static/templates/navigation.st +++ b/static/templates/navigation.st @@ -1,5 +1,9 @@ \ No newline at end of file