From 5710a89b52699e27afeff28ec4dcf07f2a3bf9e0 Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Fri, 22 Apr 2022 09:35:26 +0200 Subject: [PATCH 1/3] working on tests --- pom.xml | 2 +- .../srsoftware/widerhall/data/ListMember.java | 25 ++- .../widerhall/data/MailingList.java | 27 ++-- .../de/srsoftware/widerhall/web/Rest.java | 2 +- .../widerhall/data/ListMemberTest.java | 144 ++++++++++++++++++ 5 files changed, 178 insertions(+), 22 deletions(-) create mode 100644 src/test/java/de/srsoftware/widerhall/data/ListMemberTest.java diff --git a/pom.xml b/pom.xml index b4e7d47..f1c8a40 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.example Widerhall - 0.2.12 + 0.2.13 diff --git a/src/main/java/de/srsoftware/widerhall/data/ListMember.java b/src/main/java/de/srsoftware/widerhall/data/ListMember.java index 61807ea..78886b8 100644 --- a/src/main/java/de/srsoftware/widerhall/data/ListMember.java +++ b/src/main/java/de/srsoftware/widerhall/data/ListMember.java @@ -22,6 +22,7 @@ public class ListMember { public static final int STATE_OWNER = 1; public static final int STATE_SUBSCRIBER = 2; public static final int STATE_AWAITING_CONFIRMATION = 4; + public static final int STATE_MODERATOR = 8; private static final Logger LOG = LoggerFactory.getLogger(ListMember.class); private static final String LIST_EMAIL = "list_email"; private static final String USER_EMAIL = "user_email"; @@ -135,11 +136,23 @@ public class ListMember { return (state & testState) > 0; } + public boolean isAwaiting(){ + return hasState(STATE_AWAITING_CONFIRMATION); + } + + public boolean isModerator() { + return hasState(STATE_OWNER|STATE_MODERATOR); + } public boolean isOwner(){ return hasState(STATE_OWNER); } + public boolean isSubscriber(){ + return hasState(STATE_SUBSCRIBER|STATE_MODERATOR|STATE_OWNER); + } + + /** * return a set of list emails of MailingLists owned by the given user * @param user @@ -197,7 +210,7 @@ public class ListMember { return Map.of( EMAIL,user.email(), NAME,user.name(), - STATE,ListMember.stateText(state) + STATE,stateText() ); } @@ -219,14 +232,14 @@ public class ListMember { /** * convert state flag to readable text - * @param state * @return */ - public static String stateText(int state) { + public String stateText() { var words = new ArrayList(); - if ((state & STATE_OWNER) > 0) words.add("owner"); - if ((state & STATE_SUBSCRIBER) > 0) words.add("subscriber"); - if ((state & STATE_AWAITING_CONFIRMATION) > 0) words.add("awaiting confirmation"); + if (isAwaiting()) words.add("awaiting confirmation"); + if (isModerator()) words.add("moderator"); + if (isOwner()) words.add("owner"); + if (isSubscriber()) words.add("subscriber"); return String.join(", ",words); } diff --git a/src/main/java/de/srsoftware/widerhall/data/MailingList.java b/src/main/java/de/srsoftware/widerhall/data/MailingList.java index bc2323e..b4ed0d4 100644 --- a/src/main/java/de/srsoftware/widerhall/data/MailingList.java +++ b/src/main/java/de/srsoftware/widerhall/data/MailingList.java @@ -1,11 +1,9 @@ package de.srsoftware.widerhall.data; import de.srsoftware.widerhall.Configuration; -import de.srsoftware.widerhall.Util; import de.srsoftware.widerhall.mail.ImapClient; import de.srsoftware.widerhall.mail.MessageHandler; import de.srsoftware.widerhall.mail.SmtpClient; -import org.json.simple.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,17 +39,18 @@ public class MailingList implements MessageHandler { private static final String SMTP_PASS = "smtp_pass"; public static final String TABLE_NAME = "Lists"; private static final int STATE_PENDING = 0; - private static final int STATE_ENABLED = 1; - private static final int STATE_PUBLIC = 2; - public static final int STATE_FORWARD_FROM = 4; - public static final int STATE_FORWARD_ATTACHED = 8; - public static final int STATE_HIDE_RECEIVERS = 16; - public static final int STATE_REPLY_TO_LIST = 32; - public static final int STATE_OPEN = 64; - public static final int STATE_PUBLIC_ARCHIVE = 128; + private static final int STATE_ENABLED = 1; // do we process incoming messages? + private static final int STATE_PUBLIC = 2; // can guests see this ML? + public static final int STATE_FORWARD_FROM = 4; // set original sender as FROM when forwarding? + public static final int STATE_FORWARD_ATTACHED = 8; // forward messages as attachment? + public static final int STATE_HIDE_RECEIVERS = 16; // send using BCC receivers + public static final int STATE_REPLY_TO_LIST = 32; // set REPLY TO field to list address? + public static final int STATE_OPEN_FOR_GUESTS = 64; // allow anyone to send via this list? + public static final int STATE_PUBLIC_ARCHIVE = 128; // save received messages in archive? + public static final int STATE_OPEN_FOR_MODS = 256; // allow mods to send via this list? private static final int VISIBLE = 1; private static final int HIDDEN = 0; - private static final int DEFAULT_STATE = STATE_PENDING|STATE_HIDE_RECEIVERS|STATE_PUBLIC_ARCHIVE; + private static final int DEFAULT_STATE = STATE_PENDING|STATE_HIDE_RECEIVERS|STATE_PUBLIC_ARCHIVE|STATE_OPEN_FOR_MODS; private static final String RETAINED_FOLDER = "retained"; private final String name; private final String email; @@ -335,7 +334,7 @@ public class MailingList implements MessageHandler { Address from = message.getFrom()[0]; if (from instanceof InternetAddress internetAddress){ var senderEmail = ((InternetAddress) from).getAddress(); - if (!hasState(STATE_OPEN) && !this.hashMember(senderEmail)){ + if (!hasState(STATE_OPEN_FOR_GUESTS) && !this.hashMember(senderEmail)){ retainMessage(message); sentRetentionNotification(senderEmail); return; @@ -346,7 +345,7 @@ public class MailingList implements MessageHandler { } public MailingList open(boolean open) throws SQLException { - return setFlag(STATE_OPEN,open); + return setFlag(STATE_OPEN_FOR_GUESTS,open); } /** @@ -491,7 +490,7 @@ public class MailingList implements MessageHandler { if (hasState(STATE_FORWARD_ATTACHED)) map.put("forward_attached",HIDDEN); if (hasState(STATE_HIDE_RECEIVERS)) map.put("hide_receivers",HIDDEN); if (hasState(STATE_REPLY_TO_LIST)) map.put("reply_to_list",HIDDEN); - if (hasState(STATE_OPEN)) map.put("open",VISIBLE); + if (hasState(STATE_OPEN_FOR_GUESTS)) map.put("open",VISIBLE); if (hasState(STATE_PUBLIC_ARCHIVE)) map.put("archive",VISIBLE); return map; } diff --git a/src/main/java/de/srsoftware/widerhall/web/Rest.java b/src/main/java/de/srsoftware/widerhall/web/Rest.java index f1ff180..30c81ce 100644 --- a/src/main/java/de/srsoftware/widerhall/web/Rest.java +++ b/src/main/java/de/srsoftware/widerhall/web/Rest.java @@ -236,7 +236,7 @@ public class Rest extends HttpServlet { if (list.hasState(MailingList.STATE_FORWARD_ATTACHED)) map.put("forward_attached",true); if (list.hasState(MailingList.STATE_HIDE_RECEIVERS)) map.put("hide_receivers",true); if (list.hasState(MailingList.STATE_REPLY_TO_LIST)) map.put("reply_to_list",true); - if (list.hasState(MailingList.STATE_OPEN)) map.put("open",true); + if (list.hasState(MailingList.STATE_OPEN_FOR_GUESTS)) map.put("open",true); if (list.hasState(MailingList.STATE_PUBLIC_ARCHIVE)) map.put("archive",true); return map; } diff --git a/src/test/java/de/srsoftware/widerhall/data/ListMemberTest.java b/src/test/java/de/srsoftware/widerhall/data/ListMemberTest.java new file mode 100644 index 0000000..43f0361 --- /dev/null +++ b/src/test/java/de/srsoftware/widerhall/data/ListMemberTest.java @@ -0,0 +1,144 @@ +package de.srsoftware.widerhall.data; + +import junit.framework.TestCase; + +import java.util.Map; + +import static de.srsoftware.widerhall.Constants.*; + +public class ListMemberTest extends TestCase { + + /*public void testConfirm() { + NEEDS MOCK + }*/ + + /*public void testCreate() { + NEEDS MOCK + }*/ + + /*public void testCreateTable() { + NEEDS MOCK + }*/ + + /*public void testFrom() { + NEEDS MOCK + }*/ + + public void testHasState() { + int state = 32 | 8 | 2; + ListMember lm = new ListMember(null,null,state,null); + assertFalse(lm.hasState(1)); + assertTrue(lm.hasState(2)); + assertFalse(lm.hasState(4)); + assertTrue(lm.hasState(8)); + assertFalse(lm.hasState(16)); + assertTrue(lm.hasState(32)); + + assertTrue(lm.hasState(1|2)); + assertFalse(lm.hasState(1|4)); + assertTrue(lm.hasState(1|8)); + assertFalse(lm.hasState(1|16)); + assertTrue(lm.hasState(1|32)); + + assertTrue(lm.hasState(2|4)); + assertTrue(lm.hasState(2|8)); + assertTrue(lm.hasState(2|16)); + assertTrue(lm.hasState(2|32)); + + assertTrue(lm.hasState(4|8)); + assertFalse(lm.hasState(4|16)); + assertTrue(lm.hasState(4|32)); + + assertTrue(lm.hasState(8|16)); + assertTrue(lm.hasState(8|32)); + + assertTrue(lm.hasState(16|32)); + + assertTrue(lm.hasState(1|2|4)); + assertTrue(lm.hasState(1|2|8)); + assertTrue(lm.hasState(1|2|16)); + assertTrue(lm.hasState(1|2|32)); + assertTrue(lm.hasState(1|4|8)); + assertFalse(lm.hasState(1|4|16)); + assertTrue(lm.hasState(1|4|32)); + assertTrue(lm.hasState(1|8|16)); + assertTrue(lm.hasState(1|8|32)); + assertTrue(lm.hasState(1|16|32)); + + assertTrue(lm.hasState(2|4|8)); + assertTrue(lm.hasState(2|4|16)); + assertTrue(lm.hasState(2|4|32)); + assertTrue(lm.hasState(2|8|16)); + assertTrue(lm.hasState(2|8|32)); + assertTrue(lm.hasState(2|16|32)); + + assertTrue(lm.hasState(4|8|16)); + assertTrue(lm.hasState(4|8|32)); + assertTrue(lm.hasState(4|16|32)); + assertTrue(lm.hasState(8|16|32)); + + } + + public void testIsOwner() { + var guest = new ListMember(null,null,ListMember.STATE_SUBSCRIBER,null); + var owner = new ListMember(null,null,ListMember.STATE_OWNER,null); + var mod = new ListMember(null,null,ListMember.STATE_MODERATOR,null); + assertFalse(guest.isOwner()); + assertTrue(owner.isOwner()); + assertFalse(mod.isOwner()); + } + + public void testIsModerator() { + var guest = new ListMember(null,null,ListMember.STATE_SUBSCRIBER,null); + var owner = new ListMember(null,null,ListMember.STATE_OWNER,null); + var mod = new ListMember(null,null,ListMember.STATE_MODERATOR,null); + assertFalse(guest.isModerator()); + assertTrue(owner.isModerator()); + assertTrue(mod.isModerator()); + } + + /*public void testListsOwnedBy() { + NEEDS MOCK + }*/ + + /*public void testLoad() { + NEEDS MOCK + }*/ + + /*public void testOf() { + NEEDS MOCK + }*/ + + public void testSafeMap() { + var user = new User("email","name","salt","hash",0); + var lm = new ListMember(null,user,ListMember.STATE_AWAITING_CONFIRMATION,"token"); + assertEquals(Map.of(EMAIL,"email",NAME,"name",STATE,"awaiting confirmation"),lm.safeMap()); + } + + public void testStateText() { + var guest = new ListMember(null,null,ListMember.STATE_SUBSCRIBER,null); + var owner = new ListMember(null,null,ListMember.STATE_OWNER,null); + var mod = new ListMember(null,null,ListMember.STATE_MODERATOR,null); + var await = new ListMember(null,null,ListMember.STATE_AWAITING_CONFIRMATION,null); + + assertEquals("subscriber",guest.stateText()); + assertEquals("moderator, owner, subscriber",owner.stateText()); + assertEquals("moderator, subscriber",mod.stateText()); + assertEquals("awaiting confirmation",await.stateText()); + } + + public void testToken() { + var lm = new ListMember(null,null,0,"bam"); + assertEquals("bam",lm.token()); + } + + /*public void testUnsubscribe() { + NEEDS MOCK + }*/ + + public void testUser() { + var user = new User("email","name","salt","hash",0); + var lm = new ListMember(null,user,ListMember.STATE_AWAITING_CONFIRMATION,"token"); + assertEquals(user,lm.user()); + } +} \ No newline at end of file From 7bf492e46931f0f9ae13c7fb7c6aefeb2dca17b9 Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Fri, 22 Apr 2022 10:25:24 +0200 Subject: [PATCH 2/3] added new state bits to MailingList, implemented form for editing those --- config/logback.xml | 16 ----- pom.xml | 2 +- .../widerhall/data/MailingList.java | 45 ++++++++++-- .../de/srsoftware/widerhall/web/Rest.java | 40 ++++++----- .../java/de/srsoftware/widerhall/web/Web.java | 14 ++-- src/main/resources/logback.xml | 2 +- static/templates/inspect.st | 69 ++++++++++++------- static/templates/js.st | 11 ++- 8 files changed, 120 insertions(+), 79 deletions(-) delete mode 100644 config/logback.xml diff --git a/config/logback.xml b/config/logback.xml deleted file mode 100644 index 49a6adb..0000000 --- a/config/logback.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{5}: %msg%n - - - - - - - - - - - diff --git a/pom.xml b/pom.xml index f1c8a40..acbbd78 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.example Widerhall - 0.2.13 + 0.2.14 diff --git a/src/main/java/de/srsoftware/widerhall/data/MailingList.java b/src/main/java/de/srsoftware/widerhall/data/MailingList.java index b4ed0d4..a8cd967 100644 --- a/src/main/java/de/srsoftware/widerhall/data/MailingList.java +++ b/src/main/java/de/srsoftware/widerhall/data/MailingList.java @@ -28,6 +28,13 @@ import static de.srsoftware.widerhall.data.User.PERMISSION_ADMIN; * this class encapsulates a MailingList db object */ public class MailingList implements MessageHandler { + 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"; + public static final String KEY_REPLY_TO_LIST = "reply_to_list"; + public static final String KEY_OPEN_FOR_GUESTS = "open_for_guests"; + public static final String KEY_OPEN_FOR_SUBSCRIBERS = "open_for_subscribers"; + public static final String KEY_ARCHIVE = "archive"; private static final Logger LOG = LoggerFactory.getLogger(MailingList.class); private static final String IMAP_HOST = "imap_host"; private static final String IMAP_PORT = "imap_port"; @@ -47,10 +54,11 @@ public class MailingList implements MessageHandler { public static final int STATE_REPLY_TO_LIST = 32; // set REPLY TO field to list address? public static final int STATE_OPEN_FOR_GUESTS = 64; // allow anyone to send via this list? public static final int STATE_PUBLIC_ARCHIVE = 128; // save received messages in archive? - public static final int STATE_OPEN_FOR_MODS = 256; // allow mods to send via this list? + public static final int STATE_OPEN_FOR_SUBSCRIBERS = 256; // allow mods to send via this list? + public static final int STATE_MODS_CAN_CREATE_MODS = 512; // allow mods to make subscribers to mods? private static final int VISIBLE = 1; private static final int HIDDEN = 0; - private static final int DEFAULT_STATE = STATE_PENDING|STATE_HIDE_RECEIVERS|STATE_PUBLIC_ARCHIVE|STATE_OPEN_FOR_MODS; + 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; @@ -227,6 +235,20 @@ public class MailingList implements MessageHandler { return setFlag(STATE_HIDE_RECEIVERS,hide); } + public boolean isAllowedSender(String senderEmail){ + if (senderEmail == null || senderEmail.isBlank()) return false; + try { + var user = User.load(senderEmail); + if (user == null) return this.isOpenForGuests(); + var member = ListMember.load(this,user); + if (member == null) return this.isOpenForGuests(); + if (member.isModerator()) return true; + if (member.isSubscriber()) return this.isOpenForSubscribers(); + } catch (SQLException e) { + LOG.warn("Failed to load User for {}",senderEmail,e); + } + return this.isOpenForGuests(); + } /** * test, whether the current ML is subscribable by a given user * @param user @@ -244,6 +266,14 @@ public class MailingList implements MessageHandler { } } + public boolean isOpenForGuests(){ + return hasState(STATE_OPEN_FOR_GUESTS); + } + + public boolean isOpenForSubscribers(){ + return hasState(STATE_OPEN_FOR_GUESTS|STATE_OPEN_FOR_SUBSCRIBERS); + } + /** * Load a ML object by it's identifying email address. * This is a cached method: if the ML has been loaded before, the already-loaded object will be returned. @@ -334,7 +364,7 @@ public class MailingList implements MessageHandler { Address from = message.getFrom()[0]; if (from instanceof InternetAddress internetAddress){ var senderEmail = ((InternetAddress) from).getAddress(); - if (!hasState(STATE_OPEN_FOR_GUESTS) && !this.hashMember(senderEmail)){ + if (!isAllowedSender(senderEmail)) { retainMessage(message); sentRetentionNotification(senderEmail); return; @@ -344,10 +374,14 @@ public class MailingList implements MessageHandler { forward(message); } - public MailingList open(boolean open) throws SQLException { + public MailingList openForGuests(boolean open) throws SQLException { return setFlag(STATE_OPEN_FOR_GUESTS,open); } + public MailingList openForSubscribers(boolean open) throws SQLException { + return setFlag(STATE_OPEN_FOR_SUBSCRIBERS,open); + } + /** * provide the set of mailing lists that are publicy open to subscriptions * @return @@ -490,7 +524,8 @@ public class MailingList implements MessageHandler { if (hasState(STATE_FORWARD_ATTACHED)) map.put("forward_attached",HIDDEN); if (hasState(STATE_HIDE_RECEIVERS)) map.put("hide_receivers",HIDDEN); if (hasState(STATE_REPLY_TO_LIST)) map.put("reply_to_list",HIDDEN); - if (hasState(STATE_OPEN_FOR_GUESTS)) map.put("open",VISIBLE); + if (hasState(STATE_OPEN_FOR_GUESTS)) map.put("open_for_guests",HIDDEN); + if (hasState(STATE_OPEN_FOR_SUBSCRIBERS)) map.put("open_for_subscribers",HIDDEN); if (hasState(STATE_PUBLIC_ARCHIVE)) map.put("archive",VISIBLE); return map; } diff --git a/src/main/java/de/srsoftware/widerhall/web/Rest.java b/src/main/java/de/srsoftware/widerhall/web/Rest.java index 30c81ce..311c83e 100644 --- a/src/main/java/de/srsoftware/widerhall/web/Rest.java +++ b/src/main/java/de/srsoftware/widerhall/web/Rest.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.*; public class Rest extends HttpServlet { private static final Logger LOG = LoggerFactory.getLogger(Rest.class); @@ -56,6 +57,19 @@ public class Rest extends HttpServlet { return Map.of(SUCCESS,"Updated user permissions"); } + private List archive(HttpServletRequest req) { + var list = Util.getMailingList(req); + if (list != null){ + try { + return Post.find(list).stream().map(Post::safeMap).toList(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + LOG.debug("list: {}",list.email()); + return List.of(); + } + @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String error = handleGet(req, resp); @@ -144,18 +158,7 @@ public class Rest extends HttpServlet { } } - private List archive(HttpServletRequest req) { - var list = Util.getMailingList(req); - if (list != null){ - try { - return Post.find(list).stream().map(Post::safeMap).toList(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - LOG.debug("list: {}",list.email()); - return List.of(); - } + public String handlePost(HttpServletRequest req, HttpServletResponse resp){ @@ -232,12 +235,13 @@ public class Rest extends HttpServlet { private Map listDetail(MailingList list, User user) { if (list == null) return Map.of(ERROR,"no list email provided!"); var map = new HashMap<>(); - if (list.hasState(MailingList.STATE_FORWARD_FROM)) map.put("forward_from",true); - if (list.hasState(MailingList.STATE_FORWARD_ATTACHED)) map.put("forward_attached",true); - if (list.hasState(MailingList.STATE_HIDE_RECEIVERS)) map.put("hide_receivers",true); - if (list.hasState(MailingList.STATE_REPLY_TO_LIST)) map.put("reply_to_list",true); - if (list.hasState(MailingList.STATE_OPEN_FOR_GUESTS)) map.put("open",true); - if (list.hasState(MailingList.STATE_PUBLIC_ARCHIVE)) map.put("archive",true); + if (list.hasState(MailingList.STATE_FORWARD_FROM)) map.put(KEY_FORWARD_FROM,true); + if (list.hasState(MailingList.STATE_FORWARD_ATTACHED)) map.put(KEY_FORWARD_ATTACHED,true); + if (list.hasState(MailingList.STATE_HIDE_RECEIVERS)) map.put(KEY_HIDE_RECEIVERS,true); + if (list.hasState(MailingList.STATE_REPLY_TO_LIST)) map.put(KEY_REPLY_TO_LIST,true); + if (list.isOpenForGuests()) map.put(KEY_OPEN_FOR_GUESTS,true); + if (list.isOpenForSubscribers()) map.put(KEY_OPEN_FOR_SUBSCRIBERS,true); + if (list.hasState(MailingList.STATE_PUBLIC_ARCHIVE)) map.put(KEY_ARCHIVE,true); return map; } diff --git a/src/main/java/de/srsoftware/widerhall/web/Web.java b/src/main/java/de/srsoftware/widerhall/web/Web.java index 9b01702..721a4db 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.*; public class Web extends TemplateServlet { public static final String WEB_ROOT = "/web"; @@ -300,12 +301,13 @@ public class Web extends TemplateServlet { if (!error){ try { - list.forwardFrom(Util.getCheckbox(req, "forward_from")) - .forwardAttached(Util.getCheckbox(req, "forward_attached")) - .hideReceivers(Util.getCheckbox(req, "hide_receivers")) - .replyToList(Util.getCheckbox(req, "reply_to_list")) - .open(Util.getCheckbox(req,"open")) - .archive(Util.getCheckbox(req,"archive")); + list.forwardFrom(Util.getCheckbox(req, KEY_FORWARD_FROM)) + .forwardAttached(Util.getCheckbox(req, KEY_FORWARD_ATTACHED)) + .hideReceivers(Util.getCheckbox(req, KEY_HIDE_RECEIVERS)) + .replyToList(Util.getCheckbox(req, KEY_REPLY_TO_LIST)) + .openForGuests(Util.getCheckbox(req,KEY_OPEN_FOR_GUESTS)) + .openForSubscribers(Util.getCheckbox(req,KEY_OPEN_FOR_SUBSCRIBERS)) + .archive(Util.getCheckbox(req,KEY_ARCHIVE)); data.put(NOTES,t("Sucessfully updated MailingList!")); } catch (SQLException e){ LOG.warn("Failed to update MailingList:",e); diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 38483ff..a57f20a 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -11,6 +11,6 @@ - + diff --git a/static/templates/inspect.st b/static/templates/inspect.st index 5162fbf..3650777 100644 --- a/static/templates/inspect.st +++ b/static/templates/inspect.st @@ -11,37 +11,54 @@ «userinfo()» «messages()»

Widerhall '«data.list»' Details

- «listmembers()»
- Options for «data.list» - - - - - - + Settings +
+ Forward permissions +

+ Who is allowed to distribute mails via «data.list»? +

+ ✓ Oweners and Moderators + + +
+
+ Forward options + + + + +
+
+ Archive options + +
+ «listmembers()» diff --git a/static/templates/js.st b/static/templates/js.st index cc06e1d..fc29db7 100644 --- a/static/templates/js.st +++ b/static/templates/js.st @@ -74,12 +74,11 @@ function showListArchive(data){ } function showListDetail(data){ - if (data.forward_from) $('input[name="forward_from"]').prop('checked',true); - if (data.forward_attached) $('input[name="forward_attached"]').prop('checked',true); - if (data.hide_receivers) $('input[name="hide_receivers"]').prop('checked',true); - if (data.reply_to_list) $('input[name="reply_to_list"]').prop('checked',true); - if (data.open) $('input[name="open"]').prop('checked',true); - if (data.archive) $('input[name="archive"]').prop('checked',true); + var options = ['forward_from','forward_attached','hide_receivers','reply_to_list','open_for_guests','open_for_subscribers','archive']; + options.forEach(function(option,index,array){ + console.log(option,'→',data[option]); + if (data[option]) $('input[name="'+option+'"]').prop('checked',true); + }); } function showListOfEditableLists(data){ From b9b3196ae65e03f9820aaeda5f209275234962b1 Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Fri, 22 Apr 2022 12:35:16 +0200 Subject: [PATCH 3/3] new permissions: * list members may be nominated as moderators by admin * admin may allow moderators to nominate more moderators * admin may set allowed senders to one of the following: * owners and mods * all subscribers * everyone * moderators are now able to remove members from list --- pom.xml | 2 +- .../srsoftware/widerhall/data/ListMember.java | 149 +++++++++++++++++- .../widerhall/data/MailingList.java | 62 ++++++-- .../de/srsoftware/widerhall/web/Rest.java | 69 +++++++- .../java/de/srsoftware/widerhall/web/Web.java | 21 ++- static/templates/inspect.st | 6 +- static/templates/js.st | 29 +++- static/templates/listadminlist.st | 2 +- static/templates/listmembers.st | 1 + 9 files changed, 305 insertions(+), 36 deletions(-) diff --git a/pom.xml b/pom.xml index acbbd78..7023c6d 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.example Widerhall - 0.2.14 + 0.2.15 diff --git a/src/main/java/de/srsoftware/widerhall/data/ListMember.java b/src/main/java/de/srsoftware/widerhall/data/ListMember.java index 78886b8..b0c5554 100644 --- a/src/main/java/de/srsoftware/widerhall/data/ListMember.java +++ b/src/main/java/de/srsoftware/widerhall/data/ListMember.java @@ -1,17 +1,15 @@ package de.srsoftware.widerhall.data; import de.srsoftware.widerhall.Util; -import org.antlr.runtime.MismatchedTokenException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.stringtemplate.v4.ST; import java.sql.ResultSet; import java.sql.SQLException; import java.util.*; import static de.srsoftware.widerhall.Constants.*; -import static de.srsoftware.widerhall.Constants.STATE; +import static de.srsoftware.widerhall.Util.t; /** * @author Stephan Richter @@ -47,6 +45,37 @@ public class ListMember { this.token = token; } + public String addNewModerator(String userEmail) { + if (!isAllowedToEditMods()) return t("You are not allowed to nominate new mods for {}",list.email()); + User moderator = null; + try { + moderator = User.load(userEmail); + } catch (SQLException e) { + LOG.warn("Failed to load user for {}",userEmail,e); + return t("Failed to load user for {}",userEmail); + } + if (moderator == null) return t("No such user: {}",userEmail); + + ListMember member = null; + try { + member = ListMember.load(list,moderator); + } catch (SQLException e) { + LOG.warn("Failed to load list member for {}/{}",moderator.email(),list.email(),e); + return t("Failed to load list member for {}/{}",moderator.email(),list.email()); + } + try { + if (member == null) { + ListMember.create(list, moderator, ListMember.STATE_MODERATOR); + } else { + member.setState(ListMember.STATE_MODERATOR); + } + } catch (SQLException e) { + LOG.warn("Failed to make {} a moderator of {}",moderator.email(),list.email(),e); + return t("Failed to make {} a moderator of {}",moderator.email(),list.email()); + } + return null; + } + /** * tries to confirm the token: * This method loads the list member, that is assigned with the token. @@ -113,6 +142,68 @@ public class ListMember { Database.open().query(sql).compile().run(); } + public String dropMember(String userEmail) { + if (!isModerator()) return t("You are not allowed to remove members of {}",list.email()); + User user = null; + try { + user = User.load(userEmail); + } catch (SQLException e) { + LOG.warn("Failed to load user for {}",userEmail,e); + return t("Failed to load user for {}",userEmail); + } + if (user == null) return t("No such user: {}",userEmail); + + ListMember member = null; + try { + member = ListMember.load(list,user); + } catch (SQLException e) { + LOG.warn("Failed to load list member for {}/{}",user.email(),list.email(),e); + return t("Failed to load list member for {}/{}",user.email(),list.email()); + } + + if (member == null) return t("{} is no member of {}",user.email(),list.email()); + if (member.isOwner()) return t("You are not allowed to remvoe the list owner!"); + + try { + member.unsubscribe(); + } catch (SQLException e) { + LOG.warn("Failed to un-subscribe {} from {}",user.email(),list.email(),e); + return t("Failed to un-subscribe {} from {}",user.email(),list.email()); + } + return null; + } + + public String dropModerator(String userEmail) { + if (!isAllowedToEditMods()) return t("You are not allowed to edit mods of {}",list.email()); + User moderator = null; + try { + moderator = User.load(userEmail); + } catch (SQLException e) { + LOG.warn("Failed to load user for {}",userEmail,e); + return t("Failed to load user for {}",userEmail); + } + if (moderator == null) return t("No such user: {}",userEmail); + + ListMember member = null; + try { + member = ListMember.load(list,moderator); + } catch (SQLException e) { + LOG.warn("Failed to load list member for {}/{}",moderator.email(),list.email(),e); + return t("Failed to load list member for {}/{}",moderator.email(),list.email()); + } + try { + if (member == null) { + ListMember.create(list, moderator, ListMember.STATE_SUBSCRIBER); + } else { + member.setState(ListMember.STATE_SUBSCRIBER); + } + } catch (SQLException e) { + LOG.warn("Failed to make {} a subscriber of {}",moderator.email(),list.email(),e); + return t("Failed to make {} a subscriber of {}",moderator.email(),list.email()); + } + return null; + } + /** * create a new ListMember object from a ResultSet * @param rs @@ -136,6 +227,12 @@ public class ListMember { return (state & testState) > 0; } + public boolean isAllowedToEditMods(){ + if (isOwner()) return true; + if (isModerator()) return list.modsMayEditMods(); + return false; + } + public boolean isAwaiting(){ return hasState(STATE_AWAITING_CONFIRMATION); } @@ -152,6 +249,35 @@ public class ListMember { return hasState(STATE_SUBSCRIBER|STATE_MODERATOR|STATE_OWNER); } + public MailingList list(){ + return list; + } + /** + * return a set of list emails of MailingLists the given user attends + * @param user + * @return + */ + public static Set listsOf(User user) { + var listEmails = new HashSet(); + try { + var request = Database.open() + .select(TABLE_NAME); + if (!user.hashPermission(User.PERMISSION_ADMIN)) request = request.where(USER_EMAIL, user.email()); + var rs = request.compile().exec(); + while (rs.next()) listEmails.add(rs.getString(LIST_EMAIL)); + } catch (SQLException e) { + LOG.warn("Collecting lists of {} failed: ",user.email(),e); + } + var lists = MailingList.loadAll(listEmails); + var result = new HashSet(); + try { + for (var ml : lists) result.add(ListMember.load(ml,user)); + } catch (SQLException e) { + e.printStackTrace(); + } + + return result; + } /** * return a set of list emails of MailingLists owned by the given user @@ -230,6 +356,17 @@ public class ListMember { return this; } + public ListMember setState(int newState) throws SQLException { + Database.open() + .update(TABLE_NAME) + .set(STATE,newState) + .where(USER_EMAIL,user.email()) + .where(LIST_EMAIL,list.email()) + .compile() + .run(); + return this; + } + /** * convert state flag to readable text * @return @@ -253,11 +390,9 @@ public class ListMember { /** * unsubscribe a list member - * @param list - * @param user * @throws SQLException */ - public static void unsubscribe(MailingList list, User user) throws SQLException { + public void unsubscribe() throws SQLException { var db = Database.open(); var rs = db.select(TABLE_NAME) .where(LIST_EMAIL,list.email()) @@ -265,7 +400,7 @@ public class ListMember { .compile() .exec(); while (rs.next()){ - int state = Util.unset(rs.getInt(STATE),STATE_SUBSCRIBER,STATE_AWAITING_CONFIRMATION); // drop subscription and awaiting flags + int state = Util.unset(rs.getInt(STATE),STATE_SUBSCRIBER,STATE_MODERATOR,STATE_AWAITING_CONFIRMATION); // drop subscription and awaiting flags var req = state < 1 ? db.deleteFrom(TABLE_NAME) : db.update(TABLE_NAME).set(STATE,state).set(TOKEN,null); req.where(LIST_EMAIL,list.email()).where(USER_EMAIL,user.email()).compile().run(); } diff --git a/src/main/java/de/srsoftware/widerhall/data/MailingList.java b/src/main/java/de/srsoftware/widerhall/data/MailingList.java index a8cd967..921d13e 100644 --- a/src/main/java/de/srsoftware/widerhall/data/MailingList.java +++ b/src/main/java/de/srsoftware/widerhall/data/MailingList.java @@ -35,6 +35,7 @@ public class MailingList implements MessageHandler { public static final String KEY_OPEN_FOR_GUESTS = "open_for_guests"; public static final String KEY_OPEN_FOR_SUBSCRIBERS = "open_for_subscribers"; public static final String KEY_ARCHIVE = "archive"; + public static final String KEY_MODS_CAN_EDIT_MODS = "edit_mods"; private static final Logger LOG = LoggerFactory.getLogger(MailingList.class); private static final String IMAP_HOST = "imap_host"; private static final String IMAP_PORT = "imap_port"; @@ -55,7 +56,7 @@ public class MailingList implements MessageHandler { public static final int STATE_OPEN_FOR_GUESTS = 64; // allow anyone to send via this list? public static final int STATE_PUBLIC_ARCHIVE = 128; // save received messages in archive? public static final int STATE_OPEN_FOR_SUBSCRIBERS = 256; // allow mods to send via this list? - public static final int STATE_MODS_CAN_CREATE_MODS = 512; // allow mods to make subscribers to mods? + public static final int STATE_MODS_CAN_EDIT_MODS = 512; // allow mods to make subscribers to mods? private static final int VISIBLE = 1; private static final int HIDDEN = 0; private static final int DEFAULT_STATE = STATE_PENDING|STATE_HIDE_RECEIVERS|STATE_PUBLIC_ARCHIVE; @@ -137,17 +138,6 @@ public class MailingList implements MessageHandler { Database.open().query(sql).compile().run(); } - /** - * load the set of mailing lists a given user is allowed to edit - * @param user - * @return - */ - public static Set editableBy(User user) { - var list = new HashSet(); - for (String key : ListMember.listsOwnedBy(user)) list.add(load(key)); - return list; - } - public String email() { return email; } @@ -295,10 +285,32 @@ public class MailingList implements MessageHandler { return ml; } + /** + * Load a ML object by it's identifying email address. + * This is a cached method: if the ML has been loaded before, the already-loaded object will be returned. + * @param listEmails + * @return + */ + public static Set loadAll(Collection listEmails) { + if (listEmails == null) return null; + if (listEmails.isEmpty()) return Set.of(); + var list = new HashSet(); + try { + var rs = Database.open() + .select(TABLE_NAME) + .where(EMAIL,listEmails) + .compile().exec(); + while (rs.next()) list.add(MailingList.from(rs)); + } catch (SQLException e) { + LOG.debug("Failed to load MailingLists: ",e); + } + return list; + } + public boolean mayBeAlteredBy(User user) { if (user.hashPermission(PERMISSION_ADMIN)) return true; try { - if (ListMember.load(this,user).isOwner()) return true; + if (ListMember.load(this,user).isModerator()) return true; } catch (SQLException e) { LOG.debug("Error loading list member for ({}, {})",user.email(),email()); } @@ -343,6 +355,28 @@ public class MailingList implements MessageHandler { return map; } + /** + * load the set of mailing lists a given user is allowed to edit + * @param user + * @return + */ + public static List moderatedBy(User user) { + return ListMember.listsOf(user) + .stream() + .filter(listMember -> listMember.isModerator()) + .map(ListMember::list) + .toList(); + + } + + public boolean modsMayEditMods(){ + return hasState(STATE_MODS_CAN_EDIT_MODS); + } + + public MailingList modsMayNominateMods(boolean allowed) throws SQLException { + return setFlag(STATE_MODS_CAN_EDIT_MODS,allowed); + } + public String name(){ return name; } @@ -482,7 +516,7 @@ public class MailingList implements MessageHandler { private void sendConfirmationRequest(User user, String token) throws MessagingException, UnsupportedEncodingException { var subject = t("Please confirm your list subscription"); var config = Configuration.instance(); - var url = new StringBuilder(config.baseUrl()).append("/confirm?token=").append(token); + var url = new StringBuilder(config.baseUrl()).append("/web/confirm?token=").append(token); var text = t("Please go to {} in order to complete your list subscription!",url); smtp.send(email(),name(),user.email(),subject,text); } diff --git a/src/main/java/de/srsoftware/widerhall/web/Rest.java b/src/main/java/de/srsoftware/widerhall/web/Rest.java index 311c83e..ad70b5b 100644 --- a/src/main/java/de/srsoftware/widerhall/web/Rest.java +++ b/src/main/java/de/srsoftware/widerhall/web/Rest.java @@ -25,13 +25,16 @@ import static de.srsoftware.widerhall.data.MailingList.*; public class Rest extends HttpServlet { private static final Logger LOG = LoggerFactory.getLogger(Rest.class); + private static final String LIST_ADD_MOD = "list/add_mod"; private static final String LIST_ARCHIVE = "list/archive"; private static final String LIST_DISABLE = "list/disable"; - private static final String LIST_EDITABLE = "list/editable"; + private static final String LIST_DROP_MEMBER = "list/drop_member"; + private static final String LIST_DROP_MOD = "list/drop_mod"; private static final String LIST_DETAIL = "list/detail"; private static final String LIST_ENABLE = "list/enable"; private static final String LIST_HIDE = "list/hide"; private static final String LIST_MEMBERS = "list/members"; + private static final String LIST_MODERATED = "list/moderated"; private static final String LIST_SHOW = "list/show"; private static final String LIST_TEST = "list/test"; private static final String LIST_SUBSCRIBABLE = "list/subscribable"; @@ -119,6 +122,9 @@ public class Rest extends HttpServlet { if (user != null){ json.put(USER,user.safeMap()); switch (path) { + case LIST_ARCHIVE: + json.put("archive",archive(req)); + break; case USER_LIST: try { json.put("users", (user.hashPermission(User.PERMISSION_ADMIN) ? User.loadAll() : List.of(user)).stream().map(User::safeMap).toList()); @@ -127,8 +133,8 @@ public class Rest extends HttpServlet { json.put(ERROR,"failed to load user list"); } break; - case LIST_EDITABLE: - json.put("lists", MailingList.editableBy(user).stream().map(MailingList::safeMap).toList()); + case LIST_MODERATED: + json.put("lists", MailingList.moderatedBy(user).stream().map(MailingList::safeMap).toList()); break; case LIST_SUBSCRIBABLE: json.put("lists", MailingList.subscribable(user).stream().map(MailingList::minimalMap).toList()); @@ -173,12 +179,21 @@ public class Rest extends HttpServlet { var userEmail = req.getParameter(EMAIL); var permissions = req.getParameter(PERMISSIONS); switch (path) { + case LIST_ADD_MOD: + json.putAll(listAddMod(list,userEmail,user)); + break; case LIST_DETAIL: json.putAll(listDetail(list,user)); break; case LIST_DISABLE: json.putAll(enableList(list,user,false)); break; + case LIST_DROP_MEMBER: + json.putAll(listDropMember(list,userEmail,user)); + break; + case LIST_DROP_MOD: + json.putAll(listDropMod(list,userEmail,user)); + break; case LIST_ENABLE: json.putAll(enableList(list,user,true)); break; @@ -232,6 +247,21 @@ public class Rest extends HttpServlet { } } + private Map listAddMod(MailingList list, String userEmail, User user) { + ListMember moderator = null; + try { + moderator = ListMember.load(list,user); + } catch (SQLException e) { + LOG.warn("Failed to load list member for {}/{}",user.email(),list.email(),e); + return Map.of(ERROR,t("Failed to load list member for {}/{}",user.email(),list.email())); + } + if (moderator == null) return Map.of(ERROR,t("{} is not a member of {}",user.email(),list.email())); + + var error = moderator.addNewModerator(userEmail); + + return error == null ? Map.of(SUCCESS,t("{} is now a moderator of {}",userEmail,list.email())) : Map.of(ERROR,error); + } + private Map listDetail(MailingList list, User user) { if (list == null) return Map.of(ERROR,"no list email provided!"); var map = new HashMap<>(); @@ -242,9 +272,40 @@ public class Rest extends HttpServlet { if (list.isOpenForGuests()) map.put(KEY_OPEN_FOR_GUESTS,true); if (list.isOpenForSubscribers()) map.put(KEY_OPEN_FOR_SUBSCRIBERS,true); if (list.hasState(MailingList.STATE_PUBLIC_ARCHIVE)) map.put(KEY_ARCHIVE,true); + if (list.hasState(STATE_MODS_CAN_EDIT_MODS)) map.put(KEY_MODS_CAN_EDIT_MODS,true); return map; } + private Map listDropMember(MailingList list, String userEmail, User user) { + ListMember moderator = null; + try { + moderator = ListMember.load(list,user); + } catch (SQLException e) { + LOG.warn("Failed to load list member for {}/{}",user.email(),list.email(),e); + return Map.of(ERROR,t("Failed to load list member for {}/{}",user.email(),list.email())); + } + if (moderator == null) return Map.of(ERROR,t("{} is not a member of {}",user.email(),list.email())); + + var error = moderator.dropMember(userEmail); + + return error == null ? Map.of(SUCCESS,t("{} is now a moderator of {}",userEmail,list.email())) : Map.of(ERROR,error); + } + + private Map listDropMod(MailingList list, String userEmail, User user) { + ListMember moderator = null; + try { + moderator = ListMember.load(list,user); + } catch (SQLException e) { + LOG.warn("Failed to load list member for {}/{}",user.email(),list.email(),e); + return Map.of(ERROR,t("Failed to load list member for {}/{}",user.email(),list.email())); + } + if (moderator == null) return Map.of(ERROR,t("{} is not a member of {}",user.email(),list.email())); + + var error = moderator.dropModerator(userEmail); + + return error == null ? Map.of(SUCCESS,t("{} is now a moderator of {}",userEmail,list.email())) : Map.of(ERROR,error); + } + private Map listMembers(MailingList list, User user) { if (list == null) return Map.of(ERROR,"no list email provided!"); if (!list.membersMayBeListedBy(user)) Map.of(ERROR,t("You are not allowed to list members of '{}'",list.email())); @@ -253,7 +314,7 @@ public class Rest extends HttpServlet { .stream() .map(ListMember::safeMap) .toList(); - return Map.of(MEMBERS,members); + return Map.of(MEMBERS,members,LIST,list.minimalMap()); } catch (SQLException e) { LOG.error("Failed to load member list: ",e); return Map.of(ERROR,t("Failed to load member list '{}'",list.email())); diff --git a/src/main/java/de/srsoftware/widerhall/web/Web.java b/src/main/java/de/srsoftware/widerhall/web/Web.java index 721a4db..f2afac1 100644 --- a/src/main/java/de/srsoftware/widerhall/web/Web.java +++ b/src/main/java/de/srsoftware/widerhall/web/Web.java @@ -5,7 +5,6 @@ import de.srsoftware.widerhall.data.ListMember; import de.srsoftware.widerhall.data.MailingList; import de.srsoftware.widerhall.data.Post; import de.srsoftware.widerhall.data.User; -import org.json.simple.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -296,7 +295,7 @@ public class Web extends TemplateServlet { if (!error && !list.mayBeAlteredBy(user)) { error = true; - data.put(ERROR,t("You are not allowed to alter this list!")); + data.put(ERROR,t("You are not alter settings of this list!")); } if (!error){ @@ -305,6 +304,7 @@ public class Web extends TemplateServlet { .forwardAttached(Util.getCheckbox(req, KEY_FORWARD_ATTACHED)) .hideReceivers(Util.getCheckbox(req, KEY_HIDE_RECEIVERS)) .replyToList(Util.getCheckbox(req, KEY_REPLY_TO_LIST)) + .modsMayNominateMods(Util.getCheckbox(req, KEY_MODS_CAN_EDIT_MODS)) .openForGuests(Util.getCheckbox(req,KEY_OPEN_FOR_GUESTS)) .openForSubscribers(Util.getCheckbox(req,KEY_OPEN_FOR_SUBSCRIBERS)) .archive(Util.getCheckbox(req,KEY_ARCHIVE)); @@ -483,9 +483,22 @@ public class Web extends TemplateServlet { return loadTemplate(UNSUBSCRIBE,data,resp); } } - // if we get here, we should have a valid user + + ListMember member = null; + try { + member = ListMember.load(list,user); + } catch (SQLException e) { + LOG.debug("Failed to load list member for {}/{}",user.email(),list.email(),e); + data.put(ERROR, t("Failed to load list member for {}/{}",user.email(),list.email())); + return loadTemplate(UNSUBSCRIBE,data,resp); + } + if (member == null){ + data.put(ERROR, t("{} is no member of {}",user.email(),list.email())); + return loadTemplate(UNSUBSCRIBE,data,resp); + } + // if we get here, we should have a valid member object try { - ListMember.unsubscribe(list,user); + member.unsubscribe(); data.put(NOTES,t("Sucessfully un-subscribed from '{}'.",list.email())); return loadTemplate(INDEX,data,resp); } catch (SQLException e) { diff --git a/static/templates/inspect.st b/static/templates/inspect.st index 3650777..eba9b7a 100644 --- a/static/templates/inspect.st +++ b/static/templates/inspect.st @@ -49,11 +49,15 @@
- Archive options + Other options +
diff --git a/static/templates/js.st b/static/templates/js.st index fc29db7..7456975 100644 --- a/static/templates/js.st +++ b/static/templates/js.st @@ -1,3 +1,7 @@ +function addMod(userEmail,listEmail){ + $.post('/api/list/add_mod',{list:listEmail,email:userEmail},reload,'json'); +} + function addPermission(userEmail,permission){ if (confirm("Really give permission to "+userEmail+"?")){ $.post('/api/user/addpermission',{email:userEmail,permissions:permission},reload,'json'); @@ -12,6 +16,15 @@ function dropList(listEmail){ console.log('dopList('+listEmail+')'); } +function dropMember(userEmail,listEmail){ + $.post('/api/list/drop_member',{list:listEmail,email:userEmail},reload,'json'); +} + +function dropMod(userEmail,listEmail){ + $.post('/api/list/drop_mod',{list:listEmail,email:userEmail},reload,'json'); +} + + function dropPermission(userEmail,permission){ if (confirm("Really withdraw permission from "+userEmail+"?")){ $.post('/api/user/droppermission',{email:userEmail,permissions:permission},reload,'json'); @@ -36,8 +49,8 @@ function loadListDetail(listEmail){ $.post('/api/list/detail',{list:listEmail},showListDetail,'json'); } -function loadListOfEditableLists(){ - $.getJSON('/api/list/editable', showListOfEditableLists); +function loadListOfModeratedLists(){ + $.getJSON('/api/list/moderated', showListOfModeratedLists); } function loadListOfSubscribableLists(){ @@ -74,14 +87,14 @@ function showListArchive(data){ } function showListDetail(data){ - var options = ['forward_from','forward_attached','hide_receivers','reply_to_list','open_for_guests','open_for_subscribers','archive']; + var options = ['forward_from','forward_attached','hide_receivers','reply_to_list','open_for_guests','open_for_subscribers','archive','edit_mods']; options.forEach(function(option,index,array){ console.log(option,'→',data[option]); if (data[option]) $('input[name="'+option+'"]').prop('checked',true); }); } -function showListOfEditableLists(data){ +function showListOfModeratedLists(data){ for (let i in data.lists){ let list = data.lists[i]; let row = $(''); @@ -161,12 +174,20 @@ function showListResult(result){ } function showMembers(data){ + var list_mail = data.list.email.prefix+'@'+data.list.email.domain; for (let i in data.members){ let member = data.members[i]; let row = $(''); $('').text(member.name).appendTo(row); $('').text(member.email).appendTo(row); $('').text(member.state).appendTo(row); + let col = $(''); + console.log("data",data); + if (member.state.includes("moderator")) { + if (!member.state.includes("owner")) $('