Browse code

[cyrus] FEATBL-880 Feat: check mailboxes are known by BlueMind before allowing cyrus to create them

Thomas Cataldo authored on 09/10/2019 06:46:56
Showing 4 changed files
... ...
@@ -36,6 +36,7 @@ public class CyrusBoxes {
36 36
 	private static final Pattern userMboxRe = Pattern.compile("(.*)!user\\.([^\\.]*)\\.(.*)$");
37 37
 	private static final Pattern deletedMbox = Pattern.compile("(.*)!DELETED.user\\.([^\\.]*)\\.(.*)$");
38 38
 	private static final Pattern deletedSharedMbox = Pattern.compile("(.*)!DELETED\\.([^\\.]*)\\.(.*)$");
39
+	private static final Pattern sharedMbox = Pattern.compile("(.*)!(.*)$");
39 40
 
40 41
 	public static class ReplicatedBox {
41 42
 		public Namespace ns;
... ...
@@ -83,7 +84,7 @@ public class CyrusBoxes {
83 84
 	 * Input is "ex2016.vmw!user.tom.Deleted Messages" (or without the quotes)
84 85
 	 * 
85 86
 	 * @param fromBox
86
-	 * @return
87
+	 * @return null if the format does not match
87 88
 	 */
88 89
 	public static ReplicatedBox forCyrusMailbox(String fromMbox) {
89 90
 		fromMbox = CharMatcher.is('"').trimFrom(fromMbox);
... ...
@@ -91,6 +92,7 @@ public class CyrusBoxes {
91 92
 		Matcher userRootMatch = userMboxRootRe.matcher(fromMbox);
92 93
 		Matcher deletedMailboxMatch = deletedMbox.matcher(fromMbox);
93 94
 		Matcher deletedSharedMailboxMatch = deletedSharedMbox.matcher(fromMbox);
95
+		Matcher sharedMatch = sharedMbox.matcher(fromMbox);
94 96
 
95 97
 		if (deletedMailboxMatch.find()) {
96 98
 			String domain = deletedMailboxMatch.group(1);
... ...
@@ -134,12 +136,11 @@ public class CyrusBoxes {
134 136
 			rb.ns = Namespace.users;
135 137
 			rb.mailboxRoot = true;
136 138
 			return rb;
137
-		} else {
139
+		} else if (sharedMatch.find()) {
138 140
 			ReplicatedBox rb = new ReplicatedBox();
139 141
 			rb.ns = Namespace.shared;
140
-			int mark = fromMbox.indexOf('!');
141
-			rb.partition = fromMbox.substring(0, mark).replace('.', '_');
142
-			String afterPart = fromMbox.substring(mark + 1);
142
+			rb.partition = sharedMatch.group(1).replace('.', '_');
143
+			String afterPart = sharedMatch.group(2);
143 144
 			int dot = afterPart.indexOf('.');
144 145
 			if (dot > 0) {
145 146
 				rb.local = afterPart.substring(0, dot);
... ...
@@ -150,6 +151,9 @@ public class CyrusBoxes {
150 151
 				rb.mailboxRoot = true;
151 152
 			}
152 153
 			return rb;
154
+		} else {
155
+			logger.error("'{}' does not match a known mailbox pattern", fromMbox);
156
+			return null;
153 157
 		}
154 158
 	}
155 159
 
... ...
@@ -1,26 +1,50 @@
1 1
 package net.bluemind.backend.mail.replica.service.tests;
2 2
 
3 3
 import static org.junit.Assert.assertFalse;
4
+import static org.junit.Assert.assertNotNull;
5
+import static org.junit.Assert.assertTrue;
4 6
 
7
+import java.util.Arrays;
5 8
 import java.util.concurrent.CompletableFuture;
6
-import java.util.concurrent.ExecutionException;
7 9
 import java.util.concurrent.TimeUnit;
8
-import java.util.concurrent.TimeoutException;
9 10
 
11
+import org.junit.After;
10 12
 import org.junit.Before;
11 13
 import org.junit.Test;
12 14
 
13 15
 import net.bluemind.backend.mail.replica.api.ICyrusValidation;
14
-import net.bluemind.core.api.fault.ServerFault;
16
+import net.bluemind.core.api.Email;
15 17
 import net.bluemind.core.context.SecurityContext;
18
+import net.bluemind.core.jdbc.JdbcTestHelper;
16 19
 import net.bluemind.core.rest.ServerSideServiceProvider;
17 20
 import net.bluemind.lib.vertx.VertxPlatform;
21
+import net.bluemind.mailbox.api.IMailboxes;
22
+import net.bluemind.mailbox.api.Mailbox;
23
+import net.bluemind.mailbox.api.Mailbox.Routing;
24
+import net.bluemind.mailbox.api.Mailbox.Type;
25
+import net.bluemind.network.topology.IServiceTopology;
26
+import net.bluemind.network.topology.Topology;
27
+import net.bluemind.pool.impl.BmConfIni;
28
+import net.bluemind.server.api.Server;
29
+import net.bluemind.tests.defaultdata.PopulateHelper;
18 30
 
19 31
 public class CyrusValidationServiceTests {
20 32
 	private SecurityContext domainAdminSecurityContext;
33
+	private String backendIp;
21 34
 
22 35
 	@Before
23
-	public void before() throws InterruptedException, ExecutionException, TimeoutException {
36
+	public void before() throws Exception {
37
+		JdbcTestHelper.getInstance().beforeTest();
38
+		JdbcTestHelper.getInstance().getDbSchemaService().initialize();
39
+
40
+		BmConfIni ini = new BmConfIni();
41
+		Server cyrus = new Server();
42
+		cyrus.ip = ini.get("imap-role");
43
+		this.backendIp = cyrus.ip;
44
+		cyrus.tags = Arrays.asList("mail/imap");
45
+
46
+		PopulateHelper.initGlobalVirt(cyrus);
47
+
24 48
 		CompletableFuture<Void> startResult = new CompletableFuture<>();
25 49
 		VertxPlatform.spawnVerticles(spawnResult -> {
26 50
 			if (spawnResult.succeeded()) {
... ...
@@ -30,6 +54,37 @@ public class CyrusValidationServiceTests {
30 54
 			}
31 55
 		});
32 56
 		startResult.get(20, TimeUnit.SECONDS);
57
+
58
+		PopulateHelper.addDomain("devenv.blue", Routing.internal);
59
+
60
+		IServiceTopology topo = Topology.get();
61
+		assertNotNull(topo);
62
+
63
+		IMailboxes mboxApi = ServerSideServiceProvider.getProvider(SecurityContext.SYSTEM).instance(IMailboxes.class,
64
+				"devenv.blue");
65
+		Mailbox leslie = new Mailbox();
66
+		leslie.dataLocation = topo.any("mail/imap").uid;
67
+		leslie.name = "leslie";
68
+		leslie.type = Type.user;
69
+		leslie.routing = Routing.internal;
70
+		leslie.emails = Arrays.asList(Email.create("leslie@devenv.blue", true));
71
+		mboxApi.create("leslie", leslie);
72
+
73
+		Mailbox superVision = new Mailbox();
74
+		superVision.dataLocation = topo.any("mail/imap").uid;
75
+		superVision.name = "super.vision";
76
+		superVision.type = Type.mailshare;
77
+		superVision.routing = Routing.internal;
78
+		superVision.emails = Arrays.asList(Email.create("super.vision@devenv.blue", true));
79
+		mboxApi.create("super.vision", superVision);
80
+
81
+		System.err.println("******** BEFORE ********");
82
+	}
83
+
84
+	@After
85
+	public void after() throws Exception {
86
+		System.err.println("********* AFTER *****");
87
+		JdbcTestHelper.getInstance().afterTest();
33 88
 	}
34 89
 
35 90
 	private ICyrusValidation getService(SecurityContext context) {
... ...
@@ -43,10 +98,61 @@ public class CyrusValidationServiceTests {
43 98
 		assertFalse(result);
44 99
 	}
45 100
 
46
-	@Test(expected = ServerFault.class)
101
+	@Test
47 102
 	public void emailNullIsNotValid() {
48 103
 		ICyrusValidation cli = getService(domainAdminSecurityContext);
49 104
 		boolean result = cli.prevalidate(null, "");
50 105
 		assertFalse(result);
106
+		result = cli.prevalidate(null, null);
107
+		assertFalse(result);
108
+	}
109
+
110
+	@Test
111
+	public void subfolderOkwithNullPartition() {
112
+		ICyrusValidation cli = getService(domainAdminSecurityContext);
113
+		boolean result = cli.prevalidate("devenv.blue!user.leslie.Sent", null);
114
+		assertTrue(result);
115
+	}
116
+
117
+	@Test
118
+	public void rootFolderInValidPartition() {
119
+		ICyrusValidation cli = getService(domainAdminSecurityContext);
120
+		boolean result = cli.prevalidate("devenv.blue!user.leslie", backendIp + "__devenv_blue");
121
+		assertTrue(result);
122
+	}
123
+
124
+	@Test
125
+	public void rootFolderInWrongDomain() {
126
+		ICyrusValidation cli = getService(domainAdminSecurityContext);
127
+		boolean result = cli.prevalidate("devenv.blue!user.leslie", backendIp + "__titi_caca");
128
+		assertFalse(result);
129
+	}
130
+
131
+	@Test
132
+	public void rootFolderInInvalidPartition() {
133
+		ICyrusValidation cli = getService(domainAdminSecurityContext);
134
+		boolean result = cli.prevalidate("devenv.blue!user.leslie", "bingo");
135
+		assertFalse(result);
136
+	}
137
+
138
+	@Test
139
+	public void sharedRootInValidPartition() {
140
+		ICyrusValidation cli = getService(domainAdminSecurityContext);
141
+		boolean result = cli.prevalidate("devenv.blue!super^vision", backendIp + "__devenv_blue");
142
+		assertTrue(result);
143
+	}
144
+
145
+	@Test
146
+	public void sharedRootUnknownBackend() {
147
+		ICyrusValidation cli = getService(domainAdminSecurityContext);
148
+		boolean result = cli.prevalidate("devenv.blue!super^vision", "8.8.8.8__devenv_blue");
149
+		assertFalse(result);
150
+	}
151
+
152
+	@Test
153
+	public void sharedSubfolderInValidPartition() {
154
+		ICyrusValidation cli = getService(domainAdminSecurityContext);
155
+		boolean result = cli.prevalidate("devenv.blue!super^vision.Sent", null);
156
+		assertTrue(result);
51 157
 	}
52 158
 }
... ...
@@ -33,7 +33,7 @@ public class CyrusValidationServiceFactory
33 33
 
34 34
 	@Override
35 35
 	public ICyrusValidation instance(BmContext context, String... params) throws ServerFault {
36
-		return new CyrusValidationService();
36
+		return new CyrusValidationService(context);
37 37
 	}
38 38
 
39 39
 }
... ...
@@ -1,33 +1,76 @@
1 1
 package net.bluemind.backend.mail.replica.service.internal;
2 2
 
3
+import java.util.Optional;
4
+import java.util.Set;
5
+import java.util.stream.Collectors;
6
+
3 7
 import org.slf4j.Logger;
4 8
 import org.slf4j.LoggerFactory;
5 9
 
6 10
 import com.google.common.base.Strings;
7 11
 
12
+import net.bluemind.backend.cyrus.partitions.CyrusBoxes;
13
+import net.bluemind.backend.cyrus.partitions.CyrusBoxes.ReplicatedBox;
14
+import net.bluemind.backend.cyrus.partitions.CyrusPartition;
8 15
 import net.bluemind.backend.mail.replica.api.ICyrusValidation;
9
-import net.bluemind.core.api.fault.ErrorCode;
10
-import net.bluemind.core.api.fault.ServerFault;
16
+import net.bluemind.core.container.model.ItemValue;
17
+import net.bluemind.core.rest.BmContext;
18
+import net.bluemind.mailbox.api.IMailboxes;
19
+import net.bluemind.mailbox.api.Mailbox;
20
+import net.bluemind.network.topology.Topology;
11 21
 
12 22
 public class CyrusValidationService implements ICyrusValidation {
13 23
 	private static final Logger logger = LoggerFactory.getLogger(CyrusValidationService.class);
14 24
 
25
+	private static final String DEFAULT_PARTITION = "default";
26
+
27
+	private final BmContext ctx;
28
+
29
+	public CyrusValidationService(BmContext ctx) {
30
+		this.ctx = ctx;
31
+	}
32
+
15 33
 	@Override
16 34
 	public boolean prevalidate(String mailbox, String partition) {
17
-		logger.info("Cyrus Validation Service - prevalidate {}/{}", partition, mailbox);
35
+		// bm-master__devenv_blue/devenv.blue!user.leslie => accept
36
+		// (null)/devenv.blue!user.leslie.Sent => accept
37
+		// (null)/devenv.blue!user.titi => reject
38
+
39
+		logger.info("Prevalidate p: {} mbox: {}", partition, mailbox);
18 40
 		if (Strings.isNullOrEmpty(mailbox)) {
19
-			throw new ServerFault("Null or empty mailbox name", ErrorCode.INVALID_MAILBOX_NAME);
41
+			return false;
20 42
 		}
21
-		boolean result = true;
22
-		switch (mailbox) {
23
-		case "default":
24
-			result = false;
25
-			break;
26
-		default:
27
-			result = true;
28
-			break;
43
+
44
+		ReplicatedBox box = CyrusBoxes.forCyrusMailbox(mailbox);
45
+		if (box == null) {
46
+			return false;
29 47
 		}
30
-		return result;
48
+
49
+		if (!box.mailboxRoot) {
50
+			// null partition is fine for non-root folders
51
+			return true;
52
+		}
53
+
54
+		// mailbox root
55
+		String cleanPart = Optional.ofNullable(partition).orElse(DEFAULT_PARTITION);
56
+		if (DEFAULT_PARTITION.equals(cleanPart)) {
57
+			return false;
58
+		} else {
59
+			return validatePartition(partition, box.partition.replace('_', '.')) && validateName(box);
60
+		}
61
+	}
62
+
63
+	private boolean validateName(ReplicatedBox box) {
64
+		IMailboxes mboxApi = ctx.provider().instance(IMailboxes.class, box.partition.replace('_', '.'));
65
+		Optional<ItemValue<Mailbox>> foundBox = Optional.ofNullable(mboxApi.byName(box.local.replace('^', '.')));
66
+		return foundBox.isPresent();
67
+	}
68
+
69
+	private boolean validatePartition(String partition, String boxDomain) {
70
+		CyrusPartition parsed = CyrusPartition.forName(partition);
71
+		Set<String> backendUids = Topology.get().nodes().stream().filter(ivs -> ivs.value.tags.contains("mail/imap"))
72
+				.map(ivs -> ivs.uid).collect(Collectors.toSet());
73
+		return backendUids.contains(parsed.serverUid) && parsed.domainUid.equals(boxDomain);
31 74
 	}
32 75
 
33 76
 }