source: trunk/src/org/expeditee/auth/mail/gui/MailBay.java@ 1504

Last change on this file since 1504 was 1504, checked in by bnemhaus, 4 years ago

Revised implementation of authenticated Expeditee mail. Motivated by bugs relating to messages not being marked as read and incorrect counting of new messages for users, the Expeditee mail system has been rewritten. The new code not only does not exhibit the previous bugs but is also better engineered. Whilst the MailBay is static (which is in line with the MessageBay), the Mail class is no longer static and must be initialised for each user as they log in.

File size: 10.0 KB
Line 
1package org.expeditee.auth.mail.gui;
2
3import java.io.IOException;
4import java.nio.file.Path;
5import java.nio.file.Paths;
6import java.util.ArrayList;
7import java.util.Base64;
8import java.util.HashMap;
9import java.util.List;
10import java.util.Map;
11import java.util.Scanner;
12
13import javax.crypto.SecretKey;
14
15import org.expeditee.auth.AuthenticatorBrowser;
16import org.expeditee.auth.mail.Mail;
17import org.expeditee.auth.mail.Mail.MailEntry;
18import org.expeditee.core.Clip;
19import org.expeditee.core.Colour;
20import org.expeditee.core.Dimension;
21import org.expeditee.core.Font;
22import org.expeditee.core.Image;
23import org.expeditee.encryption.Actions;
24import org.expeditee.gio.EcosystemManager;
25import org.expeditee.gio.GraphicsManager;
26import org.expeditee.gui.DisplayController;
27import org.expeditee.gui.FrameCreator;
28import org.expeditee.gui.FrameGraphics;
29import org.expeditee.gui.FrameIO;
30import org.expeditee.gui.MessageBay;
31import org.expeditee.items.Item;
32import org.expeditee.items.Text;
33
34public class MailBay {
35 // Frameset name for mail
36 public static final String EXPEDITEE_MAIL_FRAMESET_NAME = "expediteemail";
37
38 // Font used for messages in the MailBay
39 private static final Font MESSAGE_FONT = new Font("Serif-Plain-16");
40
41 // Bay preview area constants.
42 private static final int SPACING = 25;
43 private static final int OFFSET_Y = 15;
44 private static final int OFFSET_X = 20;
45
46 private static final int MAIL_LINK_X = 50;
47
48 private static List<Item> previewMessages = new ArrayList<Item>();
49 private static FrameCreator creator;
50 private static Mail mail;
51 private static Text mailLink;
52 private static Text checkMailAction;
53
54 private static Image mailBufferImage;
55
56 public static Mail getMailClient() {
57 return mail;
58 }
59
60 public static Mail getMailClient(String user) {
61 boolean restore = mail != null;
62 String oldUser = restore ? mail.getUser() : null;
63 MailBay.reconnectToUser(user);
64 Mail ret = mail;
65 if (restore) {
66 MailBay.reconnectToUser(oldUser);
67 }
68 return ret;
69 }
70
71 public static List<Item> getPreviewMessages() {
72 return previewMessages;
73 }
74
75 public static boolean isPreviewMailItem(Item i) {
76 return previewMessages.contains(i) || i == getMailLink() || i == checkMailAction;
77 }
78
79 public static Image getImage(Clip clip, Dimension size) {
80 // Can't get an image with an invalid size
81 if (size == null || size.width <= 0 || size.height <= 0) {
82 return null;
83 }
84
85 // Update the buffer
86 updateBuffer(Item.DEFAULT_BACKGROUND, clip, size);
87
88 // Return the image buffer
89 return mailBufferImage;
90 }
91
92 public static Item getMailLink() {
93 return mailLink;
94 }
95
96 public static Item getCheckMailAction() {
97 return checkMailAction;
98 }
99
100 public static void reconnectToUser(String user) {
101 mail = new Mail(user);
102 creator = new FrameCreator(EXPEDITEE_MAIL_FRAMESET_NAME, FrameIO.MAIL_PATH,
103 EXPEDITEE_MAIL_FRAMESET_NAME, FrameCreator.ExistingFramesetOptions.AppendAfterLastItem,
104 false, AuthenticatorBrowser.PROFILEENCRYPTIONLABEL);
105 updateLink();
106 }
107
108 public static void checkMail() {
109 // Invalidate whole area
110 DisplayController.invalidateArea(DisplayController.getMessageBayPaintArea());
111 previewMessages.clear();
112
113 // Get new mail since last time we checked for mail
114 List<MailEntry> newMail = mail.checkMail();
115
116 // Place up to two previews
117 Map<String, Integer> countBySender = countBySender(newMail);
118 String[] senders = countBySender.keySet().toArray(new String[] {});
119 for (int i = 0; i < senders.length && i < 3; i++) {
120 String sender = senders[i];
121 Text text = new Text("You have received " + countBySender.get(sender) + " new messages from " + sender);
122 text.setFont(MESSAGE_FONT);
123 text.setPosition(MAIL_LINK_X, OFFSET_Y + (SPACING * (i + 1)));
124 previewMessages.add(text);
125 }
126
127 // Add new mail to creator
128 for (MailEntry mail: newMail) {
129 String timestamp = mail.getTimestamp();
130 String sender = mail.getSender();
131 generateMailHeader(timestamp, sender);
132 creator.addText("Subject: " + mail.getSubject(), Colour.BLACK, null, null, false).setFont(MESSAGE_FONT);
133 creator.addText(mail.getMessage(), Colour.BLACK, null, null, false).setFont(MESSAGE_FONT);
134 Map<String, String> optionsTextActionMap = mail.getOptionsTextActionMap();
135 String[] optionsTextActionMapKeyset = optionsTextActionMap.keySet().toArray(new String[] {});
136 for (int i = 0; i < optionsTextActionMapKeyset.length; i++) {
137 String text = optionsTextActionMapKeyset[i];
138 String[] split = text.split(":::");
139 String action = optionsTextActionMap.get(text);
140 Text t = new Text(split[0]);
141 for (int o = 1; o < split.length; o++) {
142 t.addToData(split[o]);
143 }
144 int y = OFFSET_Y + (previewMessages.size() + i) * SPACING;
145 t.setPosition(OFFSET_X, y);
146 t.setColor(Colour.BLUE);
147 t.setFont(MESSAGE_FONT);
148 t.setAction(action);
149 creator.addItem(t, false);
150 }
151 }
152
153 DisplayController.requestRefresh(true);
154 }
155
156 public static void decryptOneOffSecureMessage(SecretKey key, List<String> data) {
157 byte[] subjectBytes = Base64.getDecoder().decode(data.get(3));
158 String subject = new String(Actions.DecryptSymmetric(subjectBytes, key));
159 byte[] messageBytes = Base64.getDecoder().decode(data.get(4));
160 String message = new String(Actions.DecryptSymmetric(messageBytes, key));
161
162 creator.addText(data.get(0) + " (Single-use encrypted message)", Colour.BLACK, null, null, false);
163 creator.addText(subject, Colour.BLACK, null, null, false).setFont(MESSAGE_FONT);
164 creator.addText(message, Colour.BLACK, null, null, false).setFont(MESSAGE_FONT);
165
166 for (int i = 5; i < data.size(); i += 2) {
167 byte[] optionKeyBytes = Base64.getDecoder().decode(data.get(i));
168 String k = new String(org.expeditee.encryption.Actions.DecryptSymmetric(optionKeyBytes, key));
169 byte[] optionValueBytes = Base64.getDecoder().decode(data.get(i + 1));
170 String v = new String(org.expeditee.encryption.Actions.DecryptSymmetric(optionValueBytes, key));
171 creator.addText(k, Colour.BLACK, null, v, false).setFont(MESSAGE_FONT);
172 }
173 }
174
175 private static void generateMailHeader(String timestamp, String sender) {
176 Text header = creator.addText(timestamp + " (From: " + sender + ")", Colour.BLACK, null, null, false);
177 Path credentialsFilePath = Paths.get(FrameIO.CONTACTS_PATH).resolve(sender + "-credentials").resolve("credentials.inf");
178 if (credentialsFilePath.toFile().exists()) {
179 try (Scanner in = new Scanner(credentialsFilePath)) {
180 String line = in.nextLine();
181 int number = Integer.parseInt(line.replace(".exp", ""));
182 header.setLink(sender + "-credentials" + number);
183 } catch (IOException e) {
184 MessageBay.errorMessage("Unable to find credentials frame of message sender: " + sender);
185 }
186 }
187 }
188
189 private synchronized static void updateBuffer(Colour background, Clip clip, Dimension size) {
190 // If the buffer doesn't exist or is the wrong size, recreate it
191 if (mailBufferImage == null || !mailBufferImage.getSize().equals(size)) {
192 mailBufferImage = Image.createImage(size, true);
193 clip = null; // Need to recreate the entire image;
194 updateSize();
195 }
196
197 // Prepare graphics
198 GraphicsManager g = EcosystemManager.getGraphicsManager();
199 g.pushDrawingSurface(mailBufferImage);
200 if (clip != null) {
201 g.pushClip(clip);
202 }
203 g.setAntialiasing(true);
204 g.clear(background);
205 g.setFont(MESSAGE_FONT);
206
207 // Paint header
208 FrameGraphics.PaintItem(getHeader());
209
210 // Paint the mail messages to the bay
211 for (Item message: previewMessages) {
212 if (message == null) { continue; }
213
214 if (clip == null || clip.isNotClipped() || message.isInDrawingArea(clip.getBounds())) {
215 FrameGraphics.PaintItem(message);
216 }
217 }
218
219 // Paint the status from the MessageBay
220 Item status = MessageBay.getStatus();
221 if (status != null) {
222 FrameGraphics.PaintItem(status);
223 }
224
225 // Paint the link to the mail frame
226 if (clip == null || clip.isNotClipped() || mailLink.isInDrawingArea(clip.getBounds())) {
227 FrameGraphics.PaintItem(mailLink);
228 }
229
230 // Paint the action item to check for new mail
231 if (clip == null || clip.isNotClipped() || checkMailAction.isInDrawingArea(clip.getBounds())) {
232 FrameGraphics.PaintItem(checkMailAction);
233 }
234
235 g.popDrawingSurface();
236 }
237
238 private static void updateSize() {
239 for (Item i: previewMessages) {
240 if (i != null) {
241 i.setOffset(0, -DisplayController.getMessageBayPaintArea().getMinY());
242 }
243 }
244
245 updateLink();
246 }
247
248 private static void updateLink() {
249 mailLink = new Text(-2, "See Mail", Colour.BLACK, Colour.WHITE);
250 mailLink.setFont(MESSAGE_FONT);
251 mailLink.setOffset(0, -DisplayController.getMessageBayPaintArea().getMinY());
252 mailLink.setAnchorRight(OFFSET_X);
253 mailLink.setAnchorTop(OFFSET_Y);
254 mailLink.setLink(creator.getCurrent());
255
256 checkMailAction = new Text(-2, "Get New Mail", Colour.BLACK, Colour.WHITE);
257 checkMailAction.setFont(MESSAGE_FONT);
258 checkMailAction.setOffset(0, -DisplayController.getMessageBayPaintArea().getMinY());
259 checkMailAction.setAnchorRight(OFFSET_X);
260 checkMailAction.setAnchorTop(OFFSET_Y + SPACING);
261 checkMailAction.setAction("CheckForNewMail");
262 }
263
264 private static Text getHeader() {
265 Text header = new Text("You have [" + previewMessages.size() + "] new messages since last you check.");
266 header.setPosition(OFFSET_X, OFFSET_Y);
267 header.setOffset(0, -DisplayController.getFramePaintAreaHeight());
268 header.setFont(MESSAGE_FONT);
269 return header;
270 }
271
272 private static Map<String, Integer> countBySender(List<MailEntry> newMail) {
273 Map<String, Integer> bySenderCount = new HashMap<String, Integer>();
274
275 for (MailEntry mail: newMail) {
276 String sender = mail.getSender();
277 if (mail.isSingleUseEncryption()) {
278 sender = "Encrypted Sender";
279 }
280
281 if (bySenderCount.containsKey(sender)) {
282 Integer newValue = bySenderCount.get(sender) + 1;
283 bySenderCount.put(sender, newValue);
284 } else {
285 bySenderCount.put(sender, 1);
286 }
287 }
288
289 return bySenderCount;
290 }
291}
Note: See TracBrowser for help on using the repository browser.