Create MailFilter extension point to let plugins filter messages

We want to support different email filters in Gerrit to enable everyone
to define their own policies for incoming messages. While we expect the
majority of people to rely on what their email provider has to offer,
other including larger organisations might like to define their own
filters.

In addition, we want to create a DKIM/SPF plugin for those who want to
do this filtering in Gerrit.

Change-Id: I4d8239dccd086dc3b1e0d4e64301aa318b7ba9cd
This commit is contained in:
Patrick Hiesel
2017-01-20 11:46:43 +01:00
parent f2805cd30e
commit 30c761868f
6 changed files with 89 additions and 1 deletions

View File

@@ -2499,6 +2499,28 @@ public class MyPlugin implements ReviewerSuggestion {
----
[[mail-filter]]
== Mail Filter Plugins
Gerrit provides an extension point that enables Plugins to discard incoming
messages and prevent further processing by Gerrit.
This can be used to implement spam checks, signature validations or organisation
specific checks like IP filters.
[source, java]
----
import com.google.gerrit.extensions.annotations.ExtensionPoint;
import com.google.gerrit.server.mail.receive.MailMessage;
public class MyPlugin implements MailFilter {
boolean shouldProcessMessage(MailMessage message) {
// Implement your filter logic here
return true;
}
}
----
== SEE ALSO
* link:js-api.html[JavaScript API]

View File

@@ -136,6 +136,7 @@ import com.google.gerrit.server.group.GroupInfoCache;
import com.google.gerrit.server.group.GroupModule;
import com.google.gerrit.server.index.change.ReindexAfterUpdate;
import com.google.gerrit.server.mail.EmailModule;
import com.google.gerrit.server.mail.MailFilter;
import com.google.gerrit.server.mail.send.AddKeySender;
import com.google.gerrit.server.mail.send.AddReviewerSender;
import com.google.gerrit.server.mail.send.CreateChangeSender;
@@ -366,6 +367,7 @@ public class GerritGlobalModule extends FactoryModule {
DynamicMap.mapOf(binder(), DownloadCommand.class);
DynamicMap.mapOf(binder(), CloneCommand.class);
DynamicMap.mapOf(binder(), ReviewerSuggestion.class);
DynamicMap.mapOf(binder(), MailFilter.class);
DynamicSet.setOf(binder(), ExternalIncludedIn.class);
DynamicMap.mapOf(binder(), ProjectConfigEntry.class);
DynamicSet.setOf(binder(), PatchSetWebLink.class);

View File

@@ -0,0 +1,35 @@
// Copyright (C) 2017 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.mail;
import com.google.gerrit.extensions.annotations.ExtensionPoint;
import com.google.gerrit.server.mail.receive.MailMessage;
/**
* Listener to filter incoming email.
* <p>
* Invoked by Gerrit for each incoming email.
*/
@ExtensionPoint
public interface MailFilter {
/**
* Determine if Gerrit should discard or further process the message.
*
* @param message MailMessage parsed by Gerrit.
* @return {@code true}, if Gerrit should process the message, {@code false}
* otherwise.
*/
boolean shouldProcessMessage(MailMessage message);
}

View File

@@ -49,11 +49,18 @@ public abstract class MailMessage {
public abstract String textContent();
@Nullable
public abstract String htmlContent();
// Raw content as received over the wire
@Nullable
public abstract ImmutableList<Integer> rawContent();
@Nullable
public abstract String rawContentUTF();
public static Builder builder() {
return new AutoValue_MailMessage.Builder();
}
public abstract Builder toBuilder();
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder id(String val);
@@ -83,6 +90,8 @@ public abstract class MailMessage {
public abstract Builder subject(String val);
public abstract Builder textContent(String val);
public abstract Builder htmlContent(String val);
public abstract Builder rawContent(ImmutableList<Integer> val);
public abstract Builder rawContentUTF(String val);
public abstract MailMessage build();
}

View File

@@ -17,6 +17,7 @@ package com.google.gerrit.server.mail.receive;
import com.google.common.base.Strings;
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.extensions.client.Side;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.reviewdb.client.Account;
@@ -35,6 +36,7 @@ import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.git.BatchUpdate;
import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
import com.google.gerrit.server.git.UpdateException;
import com.google.gerrit.server.mail.MailFilter;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.InternalChangeQuery;
@@ -69,6 +71,7 @@ public class MailProcessor {
private final PatchSetUtil psUtil;
private final Provider<InternalChangeQuery> queryProvider;
private final Provider<ReviewDb> reviewDb;
private final DynamicMap<MailFilter> mailFilters;
private final Provider<String> canonicalUrl;
@Inject
@@ -81,6 +84,7 @@ public class MailProcessor {
PatchSetUtil psUtil,
Provider<InternalChangeQuery> queryProvider,
Provider<ReviewDb> reviewDb,
DynamicMap<MailFilter> mailFilters,
@CanonicalWebUrl Provider<String> canonicalUrl) {
this.accountByEmailCache = accountByEmailCache;
this.buf = buf;
@@ -91,6 +95,7 @@ public class MailProcessor {
this.psUtil = psUtil;
this.queryProvider = queryProvider;
this.reviewDb = reviewDb;
this.mailFilters = mailFilters;
this.canonicalUrl = canonicalUrl;
}
@@ -100,6 +105,15 @@ public class MailProcessor {
* @throws OrmException
*/
public void process(MailMessage message) throws OrmException {
for (DynamicMap.Entry<MailFilter> filter : mailFilters) {
if (!filter.getProvider().get().shouldProcessMessage(message)) {
log.warn("Mail: Message " + message.id() + " filtered by plugin " +
filter.getPluginName() + " " + filter.getExportName() +
". Will delete message.");
return;
}
}
MailMetadata metadata = MetadataParser.parse(message);
if (!metadata.hasRequiredFields()) {
log.error("Mail: Message " + message.id() +

View File

@@ -17,8 +17,10 @@ package com.google.gerrit.server.mail.receive;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.CharStreams;
import com.google.common.primitives.Ints;
import com.google.gerrit.server.mail.Address;
import org.apache.james.mime4j.MimeException;
@@ -52,6 +54,7 @@ public class RawMailParser {
*/
public static MailMessage parse(String raw) throws MailParsingException {
MailMessage.Builder messageBuilder = MailMessage.builder();
messageBuilder.rawContentUTF(raw);
Message mimeMessage;
try {
MessageBuilder builder = new DefaultMessageBuilder();
@@ -126,7 +129,10 @@ public class RawMailParser {
for (int c : chars) {
b.append((char) c);
}
return parse(b.toString());
MailMessage.Builder messageBuilder = parse(b.toString()).toBuilder();
messageBuilder.rawContent(ImmutableList.copyOf(Ints.asList(chars)));
return messageBuilder.build();
}
/**