Repository: james-project Updated Branches: refs/heads/master 23d22a0e9 -> 536f3caab
JAMES-2428 [DLP] Web admin endpoint to update DLP configuration Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/dec6e890 Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/dec6e890 Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/dec6e890 Branch: refs/heads/master Commit: dec6e89024f78f3ddbe7e24157d9f7d991381d54 Parents: 23d22a0 Author: duc <[email protected]> Authored: Thu Jun 14 15:49:47 2018 +0700 Committer: duc <[email protected]> Committed: Tue Jun 19 16:59:52 2018 +0700 ---------------------------------------------------------------------- .../james/modules/server/DataRoutesModules.java | 2 + .../james/webadmin/utils/JsonExtractor.java | 1 + server/protocols/webadmin/webadmin-data/pom.xml | 19 + .../org/apache/james/webadmin/DLPModule.java | 36 ++ .../james/webadmin/dto/DLPConfigurationDTO.java | 64 +++ .../webadmin/dto/DLPConfigurationItemDTO.java | 127 +++++ .../webadmin/routes/DLPConfigurationRoutes.java | 217 +++++++ .../dto/DLPConfigurationItemDTOTest.java | 105 ++++ .../routes/DLPConfigurationRoutesTest.java | 571 +++++++++++++++++++ 9 files changed, 1142 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/dec6e890/server/container/guice/protocols/webadmin-data/src/main/java/org/apache/james/modules/server/DataRoutesModules.java ---------------------------------------------------------------------- diff --git a/server/container/guice/protocols/webadmin-data/src/main/java/org/apache/james/modules/server/DataRoutesModules.java b/server/container/guice/protocols/webadmin-data/src/main/java/org/apache/james/modules/server/DataRoutesModules.java index 2ae64ec..2cb95c5 100644 --- a/server/container/guice/protocols/webadmin-data/src/main/java/org/apache/james/modules/server/DataRoutesModules.java +++ b/server/container/guice/protocols/webadmin-data/src/main/java/org/apache/james/modules/server/DataRoutesModules.java @@ -20,6 +20,7 @@ package org.apache.james.modules.server; import org.apache.james.webadmin.Routes; +import org.apache.james.webadmin.routes.DLPConfigurationRoutes; import org.apache.james.webadmin.routes.DomainsRoutes; import org.apache.james.webadmin.routes.ForwardRoutes; import org.apache.james.webadmin.routes.GroupsRoutes; @@ -33,6 +34,7 @@ public class DataRoutesModules extends AbstractModule { @Override protected void configure() { Multibinder<Routes> routesMultibinder = Multibinder.newSetBinder(binder(), Routes.class); + routesMultibinder.addBinding().to(DLPConfigurationRoutes.class); routesMultibinder.addBinding().to(DomainsRoutes.class); routesMultibinder.addBinding().to(ForwardRoutes.class); routesMultibinder.addBinding().to(GroupsRoutes.class); http://git-wip-us.apache.org/repos/asf/james-project/blob/dec6e890/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/utils/JsonExtractor.java ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/utils/JsonExtractor.java b/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/utils/JsonExtractor.java index 3f62d89..fcecc3d 100644 --- a/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/utils/JsonExtractor.java +++ b/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/utils/JsonExtractor.java @@ -20,6 +20,7 @@ package org.apache.james.webadmin.utils; import java.io.IOException; +import java.util.Collection; import java.util.List; import com.fasterxml.jackson.databind.Module; http://git-wip-us.apache.org/repos/asf/james-project/blob/dec6e890/server/protocols/webadmin/webadmin-data/pom.xml ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin/webadmin-data/pom.xml b/server/protocols/webadmin/webadmin-data/pom.xml index e6c876b..f4b1d20 100644 --- a/server/protocols/webadmin/webadmin-data/pom.xml +++ b/server/protocols/webadmin/webadmin-data/pom.xml @@ -35,6 +35,11 @@ <dependencies> <dependency> <groupId>${project.groupId}</groupId> + <artifactId>event-sourcing-event-store-memory</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> <artifactId>james-core</artifactId> </dependency> <dependency> @@ -67,6 +72,10 @@ <scope>test</scope> </dependency> <dependency> + <groupId>com.fasterxml.jackson.datatype</groupId> + <artifactId>jackson-datatype-guava</artifactId> + </dependency> + <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> </dependency> @@ -80,6 +89,16 @@ <artifactId>javax.inject</artifactId> </dependency> <dependency> + <groupId>net.javacrumbs.json-unit</groupId> + <artifactId>json-unit-fluent</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>nl.jqno.equalsverifier</groupId> + <artifactId>equalsverifier</artifactId> + <scope>test</scope> + </dependency> + <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <scope>test</scope> http://git-wip-us.apache.org/repos/asf/james-project/blob/dec6e890/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/DLPModule.java ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/DLPModule.java b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/DLPModule.java new file mode 100644 index 0000000..0363ca3 --- /dev/null +++ b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/DLPModule.java @@ -0,0 +1,36 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you 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 org.apache.james.webadmin; + +import org.apache.james.webadmin.utils.JsonTransformerModule; + +import com.fasterxml.jackson.databind.Module; +import com.fasterxml.jackson.datatype.guava.GuavaModule; + +public class DLPModule implements JsonTransformerModule { + + public DLPModule() { + } + + @Override + public Module asJacksonModule() { + return new GuavaModule(); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/dec6e890/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/dto/DLPConfigurationDTO.java ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/dto/DLPConfigurationDTO.java b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/dto/DLPConfigurationDTO.java new file mode 100644 index 0000000..d1265a6 --- /dev/null +++ b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/dto/DLPConfigurationDTO.java @@ -0,0 +1,64 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you 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 org.apache.james.webadmin.dto; + +import java.util.List; + +import org.apache.james.dlp.api.DLPConfigurationItem; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.steveash.guavate.Guavate; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; + +public class DLPConfigurationDTO { + + public static List<DLPConfigurationItem> toDLPConfigurations(DLPConfigurationDTO dto) { + Preconditions.checkNotNull(dto); + + return dto.rules + .stream() + .map(DLPConfigurationItemDTO::toDLPConfiguration) + .collect(Guavate.toImmutableList()); + } + + public static DLPConfigurationDTO toDTO(List<DLPConfigurationItem> dlpConfigurations) { + Preconditions.checkNotNull(dlpConfigurations); + + return new DLPConfigurationDTO( + dlpConfigurations + .stream() + .map(DLPConfigurationItemDTO::toDTO) + .collect(Guavate.toImmutableList())); + } + + private final ImmutableList<DLPConfigurationItemDTO> rules; + + @JsonCreator + public DLPConfigurationDTO( + @JsonProperty("rules") ImmutableList<DLPConfigurationItemDTO> rules) { + this.rules = rules; + } + + public ImmutableList<DLPConfigurationItemDTO> getRules() { + return rules; + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/dec6e890/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/dto/DLPConfigurationItemDTO.java ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/dto/DLPConfigurationItemDTO.java b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/dto/DLPConfigurationItemDTO.java new file mode 100644 index 0000000..502615a --- /dev/null +++ b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/dto/DLPConfigurationItemDTO.java @@ -0,0 +1,127 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you 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 org.apache.james.webadmin.dto; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import org.apache.james.dlp.api.DLPConfigurationItem; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.steveash.guavate.Guavate; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; + +public class DLPConfigurationItemDTO { + + @VisibleForTesting + static DLPConfigurationItem toDLPConfiguration(DLPConfigurationItemDTO dto) { + return DLPConfigurationItem.builder() + .id(DLPConfigurationItem.Id.of(dto.id)) + .expression(dto.expression) + .explanation(dto.explanation) + .targetsSender(dto.targetsSender) + .targetsRecipients(dto.targetsRecipients) + .targetsContent(dto.targetsContent) + .build(); + } + + @VisibleForTesting + static DLPConfigurationItemDTO toDTO(DLPConfigurationItem dlpConfiguration) { + DLPConfigurationItem.Targets targets = dlpConfiguration.getTargets(); + return new DLPConfigurationItemDTO( + dlpConfiguration.getId().asString(), + dlpConfiguration.getRegexp(), + dlpConfiguration.getExplanation(), + Optional.of(targets.isSenderTargeted()), + Optional.of(targets.isRecipientTargeted()), + Optional.of(targets.isContentTargeted())); + } + + private final String id; + private final String expression; + private final Optional<String> explanation; + private final Optional<Boolean> targetsSender; + private final Optional<Boolean> targetsRecipients; + private final Optional<Boolean> targetsContent; + + @JsonCreator + public DLPConfigurationItemDTO(@JsonProperty("id") String id, + @JsonProperty("expression") String expression, + @JsonProperty("explanation") Optional<String> explanation, + @JsonProperty("targetsSender") Optional<Boolean> targetsSender, + @JsonProperty("targetsRecipients") Optional<Boolean> targetsRecipients, + @JsonProperty("targetsContent") Optional<Boolean> targetsContent) { + Preconditions.checkNotNull(id, "'id' is mandatory"); + Preconditions.checkNotNull(expression, "'expression' is mandatory"); + this.id = id; + this.expression = expression; + this.explanation = explanation; + this.targetsSender = targetsSender; + this.targetsRecipients = targetsRecipients; + this.targetsContent = targetsContent; + } + + public String getId() { + return id; + } + + public String getExpression() { + return expression; + } + + public Optional<String> getExplanation() { + return explanation; + } + + public Optional<Boolean> getTargetsSender() { + return targetsSender; + } + + public Optional<Boolean> getTargetsRecipients() { + return targetsRecipients; + } + + public Optional<Boolean> getTargetsContent() { + return targetsContent; + } + + @Override + public final boolean equals(Object o) { + if (o instanceof DLPConfigurationItemDTO) { + DLPConfigurationItemDTO that = (DLPConfigurationItemDTO) o; + + return Objects.equals(this.id, that.id) + && Objects.equals(this.expression, that.expression) + && Objects.equals(this.explanation, that.explanation) + && Objects.equals(this.targetsSender, that.targetsSender) + && Objects.equals(this.targetsRecipients, that.targetsRecipients) + && Objects.equals(this.targetsContent, that.targetsContent); + } + return false; + } + + @Override + public final int hashCode() { + return Objects.hash(id, expression, explanation, targetsSender, targetsRecipients, targetsContent); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/dec6e890/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/DLPConfigurationRoutes.java ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/DLPConfigurationRoutes.java b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/DLPConfigurationRoutes.java new file mode 100644 index 0000000..73b1d33 --- /dev/null +++ b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/DLPConfigurationRoutes.java @@ -0,0 +1,217 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you 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 org.apache.james.webadmin.routes; + +import static com.google.common.net.HttpHeaders.CONTENT_TYPE; +import static org.apache.james.webadmin.Constants.EMPTY_BODY; +import static org.apache.james.webadmin.Constants.JSON_CONTENT_TYPE; +import static org.apache.james.webadmin.Constants.SEPARATOR; + +import java.util.List; + +import javax.inject.Inject; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; + +import org.apache.james.core.Domain; +import org.apache.james.dlp.api.DLPConfigurationItem; +import org.apache.james.dlp.api.DLPConfigurationStore; +import org.apache.james.domainlist.api.DomainList; +import org.apache.james.domainlist.api.DomainListException; +import org.apache.james.webadmin.Routes; +import org.apache.james.webadmin.dto.DLPConfigurationDTO; +import org.apache.james.webadmin.utils.ErrorResponder; +import org.apache.james.webadmin.utils.ErrorResponder.ErrorType; +import org.apache.james.webadmin.utils.JsonExtractor; +import org.apache.james.webadmin.utils.JsonTransformer; +import org.eclipse.jetty.http.HttpStatus; + +import com.fasterxml.jackson.datatype.guava.GuavaModule; +import com.github.steveash.guavate.Guavate; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; +import spark.HaltException; +import spark.Request; +import spark.Service; + +@Api(tags = "DLPRules") +@Path(DLPConfigurationRoutes.BASE_PATH) +@Produces(JSON_CONTENT_TYPE) +public class DLPConfigurationRoutes implements Routes { + + static final String BASE_PATH = "/dlp/rules"; + + private static final String DOMAIN_NAME = ":senderDomain"; + private static final String SPECIFIC_DLP_RULE_DOMAIN = BASE_PATH + SEPARATOR + DOMAIN_NAME; + + private final JsonTransformer jsonTransformer; + private final DLPConfigurationStore dlpConfigurationStore; + private final JsonExtractor<DLPConfigurationDTO> jsonExtractor; + private final DomainList domainList; + + private Service service; + + @Inject + public DLPConfigurationRoutes(DLPConfigurationStore dlpConfigurationStore, DomainList domainList, JsonTransformer jsonTransformer) { + this.dlpConfigurationStore = dlpConfigurationStore; + this.domainList = domainList; + this.jsonTransformer = jsonTransformer; + this.jsonExtractor = new JsonExtractor<>(DLPConfigurationDTO.class, new GuavaModule()); + } + + @Override + public void define(Service service) { + this.service = service; + + defineStore(); + + defineList(); + + defineClear(); + } + + @PUT + @Path("/{senderDomain}") + @ApiOperation(value = "Store a list of dlp configs for given senderDomain") + @ApiImplicitParams({ + @ApiImplicitParam(required = true, dataType = "string", name = "senderDomain", paramType = "path") + }) + @ApiResponses(value = { + @ApiResponse(code = HttpStatus.NO_CONTENT_204, message = "OK. dlp config is stored."), + @ApiResponse(code = HttpStatus.BAD_REQUEST_400, message = "Invalid senderDomain or payload in request"), + @ApiResponse(code = HttpStatus.NOT_FOUND_404, message = "The domain does not exist."), + @ApiResponse(code = HttpStatus.INTERNAL_SERVER_ERROR_500, + message = "Internal server error - Something went bad on the server side.") + }) + public void defineStore() { + service.put(SPECIFIC_DLP_RULE_DOMAIN, (request, response) -> { + Domain senderDomain = parseDomain(request); + DLPConfigurationDTO dto = jsonExtractor.parse(request.body()); + + dlpConfigurationStore.store(senderDomain, DLPConfigurationDTO.toDLPConfigurations(dto)); + + response.status(HttpStatus.NO_CONTENT_204); + return EMPTY_BODY; + }); + } + + @GET + @Path("/{senderDomain}") + @ApiOperation(value = "Retrieve a list of dlp configs for given senderDomain") + @ApiImplicitParams({ + @ApiImplicitParam(required = true, dataType = "string", name = "senderDomain", paramType = "path") + }) + @ApiResponses(value = { + @ApiResponse(code = HttpStatus.OK_200, message = "OK. dlp configs returned"), + @ApiResponse(code = HttpStatus.BAD_REQUEST_400, message = "Invalid senderDomain in request"), + @ApiResponse(code = HttpStatus.NOT_FOUND_404, message = "The domain does not exist."), + @ApiResponse(code = HttpStatus.INTERNAL_SERVER_ERROR_500, + message = "Internal server error - Something went bad on the server side.") + }) + public void defineList() { + service.get(SPECIFIC_DLP_RULE_DOMAIN, (request, response) -> { + Domain senderDomain = parseDomain(request); + List<DLPConfigurationItem> dlpConfigurations = dlpConfigurationStore + .list(senderDomain) + .collect(Guavate.toImmutableList()); + + DLPConfigurationDTO dto = DLPConfigurationDTO.toDTO(dlpConfigurations); + response.status(HttpStatus.OK_200); + response.header(CONTENT_TYPE, JSON_CONTENT_TYPE); + return dto; + }, jsonTransformer); + } + + @DELETE + @Path("/{senderDomain}") + @ApiOperation(value = "Clear all dlp configs for given senderDomain") + @ApiImplicitParams({ + @ApiImplicitParam(required = true, dataType = "string", name = "senderDomain", paramType = "path") + }) + @ApiResponses(value = { + @ApiResponse(code = HttpStatus.NO_CONTENT_204, message = "OK. dlp configs are cleared"), + @ApiResponse(code = HttpStatus.BAD_REQUEST_400, message = "Invalid senderDomain in request"), + @ApiResponse(code = HttpStatus.NOT_FOUND_404, message = "The domain does not exist."), + @ApiResponse(code = HttpStatus.INTERNAL_SERVER_ERROR_500, + message = "Internal server error - Something went bad on the server side.") + }) + public void defineClear() { + service.delete(SPECIFIC_DLP_RULE_DOMAIN, (request, response) -> { + Domain senderDomain = parseDomain(request); + dlpConfigurationStore.clear(senderDomain); + + response.status(HttpStatus.NO_CONTENT_204); + return EMPTY_BODY; + }, jsonTransformer); + } + + private Domain parseDomain(Request request) { + String domainName = request.params(DOMAIN_NAME); + try { + Domain domain = Domain.of(domainName); + validateDomainInList(domain); + + return domain; + } catch (IllegalArgumentException e) { + throw invalidDomain(String.format("Invalid request for domain: %s", domainName), e); + } catch (DomainListException e) { + throw serverError(String.format("Cannot recognize domain: %s in domain list", domainName), e); + } + } + + private void validateDomainInList(Domain domain) throws DomainListException { + if (!domainList.containsDomain(domain)) { + throw notFound(String.format("'%s' is not managed by this James server", domain.name())); + } + } + + private HaltException invalidDomain(String message, Exception e) { + return ErrorResponder.builder() + .statusCode(HttpStatus.BAD_REQUEST_400) + .type(ErrorType.INVALID_ARGUMENT) + .message(message) + .cause(e) + .haltError(); + } + + private HaltException serverError(String message, Exception e) { + return ErrorResponder.builder() + .statusCode(HttpStatus.INTERNAL_SERVER_ERROR_500) + .type(ErrorType.SERVER_ERROR) + .message(message) + .cause(e) + .haltError(); + } + + private HaltException notFound(String message) { + return ErrorResponder.builder() + .statusCode(HttpStatus.NOT_FOUND_404) + .type(ErrorType.INVALID_ARGUMENT) + .message(message) + .haltError(); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/dec6e890/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/dto/DLPConfigurationItemDTOTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/dto/DLPConfigurationItemDTOTest.java b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/dto/DLPConfigurationItemDTOTest.java new file mode 100644 index 0000000..a4b0da8 --- /dev/null +++ b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/dto/DLPConfigurationItemDTOTest.java @@ -0,0 +1,105 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you 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 org.apache.james.webadmin.dto; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.List; +import java.util.Optional; + +import org.apache.james.dlp.api.DLPConfigurationItem; +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.Test; + +import com.google.common.collect.ImmutableList; + +class DLPConfigurationItemDTOTest { + + private static final String ID = "id"; + private static final String EXPRESSION = "expression"; + private static final String EXPLANATION = "explanation"; + private static final String NULL_ID = null; + private static final String NULL_EXPRESSION = null; + + @Test + void toDTOsShouldBeSetAllFields() { + DLPConfigurationItemDTO dto = DLPConfigurationItemDTO.toDTO( + DLPConfigurationItem.builder() + .id(DLPConfigurationItem.Id.of(ID)) + .expression(EXPRESSION) + .explanation(EXPLANATION) + .targetsSender(Optional.of(true)) + .targetsRecipients(Optional.of(true)) + .targetsContent(Optional.of(true)) + .build()); + + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(dto.getId()).isEqualTo(ID); + softly.assertThat(dto.getExpression()).isEqualTo(EXPRESSION); + softly.assertThat(dto.getExplanation().get()).isEqualTo(EXPLANATION); + softly.assertThat(dto.getTargetsSender().get()).isTrue(); + softly.assertThat(dto.getTargetsRecipients().get()).isTrue(); + softly.assertThat(dto.getTargetsContent().get()).isTrue(); + }); + } + + @Test + void toDLPConfigurationsShouldBeSetAllFields() { + DLPConfigurationItem item = DLPConfigurationItemDTO.toDLPConfiguration( + new DLPConfigurationItemDTO( + ID, + EXPRESSION, + Optional.of(EXPLANATION), + Optional.of(true), + Optional.of(true), + Optional.of(true))); + + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(item.getId().asString()).isEqualTo(ID); + softly.assertThat(item.getRegexp()).isEqualTo(EXPRESSION); + softly.assertThat(item.getExplanation().get()).isEqualTo(EXPLANATION); + softly.assertThat(item.getTargets().isSenderTargeted()).isTrue(); + softly.assertThat(item.getTargets().isRecipientTargeted()).isTrue(); + softly.assertThat(item.getTargets().isContentTargeted()).isTrue(); + }); + } + + @Test + void constructorShouldThrowWhenIdIsNull() { + assertThatThrownBy(() -> new DLPConfigurationItemDTO(NULL_ID, + EXPRESSION, + Optional.of(EXPLANATION), + Optional.of(true), + Optional.of(true), + Optional.of(true))) + .isInstanceOf(NullPointerException.class); + } + + @Test + void constructorShouldThrowWhenExpressionIsNull() { + assertThatThrownBy(() -> new DLPConfigurationItemDTO(ID, + NULL_EXPRESSION, + Optional.of(EXPLANATION), + Optional.of(true), + Optional.of(true), + Optional.of(true))) + .isInstanceOf(NullPointerException.class); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/dec6e890/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/DLPConfigurationRoutesTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/DLPConfigurationRoutesTest.java b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/DLPConfigurationRoutesTest.java new file mode 100644 index 0000000..74d3468 --- /dev/null +++ b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/DLPConfigurationRoutesTest.java @@ -0,0 +1,571 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you 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 org.apache.james.webadmin.routes; + +import static com.jayway.restassured.RestAssured.given; +import static com.jayway.restassured.RestAssured.requestSpecification; +import static com.jayway.restassured.RestAssured.when; +import static com.jayway.restassured.RestAssured.with; +import static net.javacrumbs.jsonunit.fluent.JsonFluentAssert.assertThatJson; +import static org.apache.james.webadmin.Constants.JSON_CONTENT_TYPE; +import static org.apache.james.webadmin.WebAdminServer.NO_CONFIGURATION; +import static org.hamcrest.Matchers.is; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; + +import java.net.InetAddress; +import java.util.List; + +import org.apache.james.core.Domain; +import org.apache.james.dlp.api.DLPConfigurationItem; +import org.apache.james.dlp.api.DLPConfigurationStore; +import org.apache.james.dlp.eventsourcing.EventSourcingDLPConfigurationStore; +import org.apache.james.dnsservice.api.DNSService; +import org.apache.james.domainlist.api.DomainList; +import org.apache.james.domainlist.memory.MemoryDomainList; +import org.apache.james.eventsourcing.eventstore.memory.InMemoryEventStore; +import org.apache.james.metrics.logger.DefaultMetricFactory; +import org.apache.james.webadmin.DLPModule; +import org.apache.james.webadmin.WebAdminServer; +import org.apache.james.webadmin.WebAdminUtils; +import org.apache.james.webadmin.utils.JsonTransformer; +import org.eclipse.jetty.http.HttpStatus; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import com.google.common.collect.ImmutableList; +import com.jayway.restassured.RestAssured; +import com.jayway.restassured.http.ContentType; +import com.jayway.restassured.specification.RequestSpecification; + +class DLPConfigurationRoutesTest { + + private static final String DEFAULT_DOMAIN = "james.org"; + private static final Domain SENDER_DOMAIN = Domain.of(DEFAULT_DOMAIN); + private static final String DOMAIN_2 = "apache.org"; + private static final Domain SENDER_DOMAIN_2 = Domain.of(DOMAIN_2); + + private static final DLPConfigurationItem CONFIGURATION_ITEM_1 = DLPConfigurationItem.builder() + .id(DLPConfigurationItem.Id.of("1")) + .explanation("explanation 1") + .expression(DEFAULT_DOMAIN) + .targetsSender() + .targetsRecipients() + .targetsContent() + .build(); + private static final DLPConfigurationItem CONFIGURATION_ITEM_2 = DLPConfigurationItem.builder() + .id(DLPConfigurationItem.Id.of("2")) + .expression(DEFAULT_DOMAIN) + .targetsSender() + .build(); + + private static final List<DLPConfigurationItem> CONFIGURATION_ITEMS_FOR_DOMAIN_2 = ImmutableList.of(DLPConfigurationItem.builder() + .id(DLPConfigurationItem.Id.of("3")) + .expression(DOMAIN_2) + .targetsSender() + .build()); + + private static final List<DLPConfigurationItem> CONFIGURATION_ITEMS = ImmutableList.of(CONFIGURATION_ITEM_1, CONFIGURATION_ITEM_2); + + private WebAdminServer webAdminServer; + private EventSourcingDLPConfigurationStore dlpStore; + + private void createServer(DLPConfigurationStore dlpConfigurationStore, DomainList domainList) throws Exception { + webAdminServer = WebAdminUtils.createWebAdminServer( + new DefaultMetricFactory(), + new DLPConfigurationRoutes(dlpConfigurationStore, domainList, new JsonTransformer(new DLPModule()))); + webAdminServer.configure(NO_CONFIGURATION); + webAdminServer.await(); + + requestSpecification = buildRequestSpecification(webAdminServer); + } + + RequestSpecification buildRequestSpecification(WebAdminServer server) { + RestAssured.enableLoggingOfRequestAndResponseIfValidationFails(); + + return WebAdminUtils + .buildRequestSpecification(server) + .setBasePath(DLPConfigurationRoutes.BASE_PATH) + .build(); + } + + + @BeforeEach + void setup() throws Exception { + DNSService dnsService = mock(DNSService.class); + Mockito.when(dnsService.getHostName(any())).thenReturn("localhost"); + Mockito.when(dnsService.getLocalHost()).thenReturn(InetAddress.getByName("localhost")); + + MemoryDomainList domainList = new MemoryDomainList(dnsService); + domainList.setAutoDetectIP(false); + domainList.setAutoDetect(false); + domainList.addDomain(SENDER_DOMAIN); + domainList.addDomain(SENDER_DOMAIN_2); + + dlpStore = new EventSourcingDLPConfigurationStore(new InMemoryEventStore()); + createServer(dlpStore, domainList); + } + + + @Nested + class DefineStore { + + @Test + void putShouldStoreTheConfigurations() { + String storeBody = + "{\"rules\": [" + + " {" + + " \"id\": \"1\"," + + " \"expression\": \"expression 1\"," + + " \"explanation\": \"explanation 1\"," + + " \"targetsSender\": true," + + " \"targetsRecipients\": true," + + " \"targetsContent\": true" + + " }," + + " {" + + " \"id\": \"2\"," + + " \"expression\": \"expression 2\"," + + " \"explanation\": \"explanation 2\"," + + " \"targetsSender\": false," + + " \"targetsRecipients\": false," + + " \"targetsContent\": false" + + " }]}"; + + given() + .body(storeBody) + .when() + .put(DEFAULT_DOMAIN) + .then() + .statusCode(HttpStatus.NO_CONTENT_204); + + String retrievedBody = with() + .get(DEFAULT_DOMAIN) + .then() + .statusCode(HttpStatus.OK_200) + .contentType(ContentType.JSON) + .extract() + .body().asString(); + + assertThatJson(retrievedBody).isEqualTo(storeBody); + } + + @Test + void putShouldStoreTheConfigurationsWhenTargetsAreNotSpecified() { + String storeBody = + "{\"rules\": [" + + " {" + + " \"id\": \"3\"," + + " \"expression\": \"expression 3\"," + + " \"explanation\": \"explanation 3\"" + + " }]}"; + + given() + .body(storeBody) + .when() + .put(DEFAULT_DOMAIN) + .then() + .statusCode(HttpStatus.NO_CONTENT_204); + + String retrievedBody = with() + .get(DEFAULT_DOMAIN) + .then() + .statusCode(HttpStatus.OK_200) + .contentType(ContentType.JSON) + .extract() + .body().asString(); + + assertThatJson(retrievedBody) + .isEqualTo( + "{\"rules\": [" + + " {" + + " \"id\": \"3\"," + + " \"expression\": \"expression 3\"," + + " \"explanation\": \"explanation 3\"," + + " \"targetsSender\": false," + + " \"targetsRecipients\": false," + + " \"targetsContent\": false" + + " }" + + "]}"); + } + + @Test + void putShouldStoreTheConfigurationsWhenExplanationNotSpecified() { + String storeBody = + "{\"rules\": [{" + + " \"id\": \"3\"," + + " \"expression\": \"expression 3\"" + + "}]}"; + + given() + .body(storeBody) + .when() + .put(DEFAULT_DOMAIN) + .then() + .statusCode(HttpStatus.NO_CONTENT_204); + + String retrievedBody = with() + .get(DEFAULT_DOMAIN) + .then() + .statusCode(HttpStatus.OK_200) + .contentType(ContentType.JSON) + .extract() + .body().asString(); + + assertThatJson(retrievedBody) + .isEqualTo( + "{\"rules\": [" + + " {" + + " \"id\": \"3\"," + + " \"expression\": \"expression 3\"," + + " \"explanation\": null," + + " \"targetsSender\": false," + + " \"targetsRecipients\": false," + + " \"targetsContent\": false" + + " }" + + "]}"); + } + + @Test + void putShouldReturnBadRequestWhenIdIsNotSpecified() { + String body = + "{" + + " \"expression\": \"expression 4\"," + + " \"explanation\": \"explanation 4\"," + + " \"targetsSender\": false," + + " \"targetsRecipients\": false," + + " \"targetsContent\": false" + + "}"; + + given() + .body(body) + .when() + .put(DEFAULT_DOMAIN) + .then() + .statusCode(HttpStatus.BAD_REQUEST_400) + .contentType(JSON_CONTENT_TYPE) + .body("statusCode", is(400)) + .body("type", is("InvalidArgument")) + .body("message", is("JSON payload of the request is not valid")); + } + + @Test + void putShouldReturnBadRequestWhenExpressionIsNotSpecified() { + String body = + "{" + + " \"id\": \"5\"," + + " \"explanation\": \"explanation 5\"," + + " \"targetsSender\": false," + + " \"targetsRecipients\": false," + + " \"targetsContent\": false" + + "}"; + + given() + .body(body) + .when() + .put(DEFAULT_DOMAIN) + .then() + .statusCode(HttpStatus.BAD_REQUEST_400) + .contentType(JSON_CONTENT_TYPE) + .body("statusCode", is(400)) + .body("type", is("InvalidArgument")) + .body("message", is("JSON payload of the request is not valid")); + } + + @Test + void putShouldReturnNotFoundWhenDomainNotInList() { + String body = + "[{" + + " \"id\": \"1\"," + + " \"expression\": \"expression 1\"," + + " \"explanation\": \"explanation 1\"," + + " \"targetsSender\": true," + + " \"targetsRecipients\": true," + + " \"targetsContent\": true" + + "}," + + "{" + + " \"id\": \"2\"," + + " \"expression\": \"expression 2\"," + + " \"explanation\": \"explanation 2\"," + + " \"targetsSender\": false," + + " \"targetsRecipients\": false," + + " \"targetsContent\": false" + + "}]"; + + given() + .body(body) + .when() + .put("strange.com") + .then() + .statusCode(HttpStatus.NOT_FOUND_404) + .contentType(JSON_CONTENT_TYPE) + .body("statusCode", is(HttpStatus.NOT_FOUND_404)) + .body("type", is("InvalidArgument")) + .body("message", is("'strange.com' is not managed by this James server")); + } + + @Test + void putShouldReturnBadRequestWhenDomainIsNotValid() { + String body = + "[{" + + " \"id\": \"1\"," + + " \"expression\": \"expression 1\"," + + " \"explanation\": \"explanation 1\"," + + " \"targetsSender\": true," + + " \"targetsRecipients\": true," + + " \"targetsContent\": true" + + "}," + + "{" + + " \"id\": \"2\"," + + " \"expression\": \"expression 2\"," + + " \"explanation\": \"explanation 2\"," + + " \"targetsSender\": false," + + " \"targetsRecipients\": false," + + " \"targetsContent\": false" + + "}]"; + + given() + .body(body) + .when() + .put("[email protected]") + .then() + .statusCode(HttpStatus.BAD_REQUEST_400) + .contentType(JSON_CONTENT_TYPE) + .body("statusCode", is(HttpStatus.BAD_REQUEST_400)) + .body("type", is("InvalidArgument")) + .body("message", is("Invalid request for domain: [email protected]")); + } + } + + @Nested + class DefineClear { + + @Test + void deleteShouldRemoveTheConfigurations() { + dlpStore.store(SENDER_DOMAIN, CONFIGURATION_ITEMS); + + when() + .delete(DEFAULT_DOMAIN) + .then() + .statusCode(HttpStatus.NO_CONTENT_204); + + String retrievedBody = with() + .get(DEFAULT_DOMAIN) + .then() + .statusCode(HttpStatus.OK_200) + .contentType(ContentType.JSON) + .extract() + .body().asString(); + + assertThatJson(retrievedBody).isEqualTo("{\"rules\":[]}"); + } + + @Test + void deleteShouldRemoveOnlyConfigurationsFromCorrespondingDomain() { + dlpStore.store(SENDER_DOMAIN, CONFIGURATION_ITEMS); + dlpStore.store(SENDER_DOMAIN_2, CONFIGURATION_ITEMS_FOR_DOMAIN_2); + + when() + .delete(DEFAULT_DOMAIN) + .then() + .statusCode(HttpStatus.NO_CONTENT_204); + + String retrievedBody = with() + .get(DEFAULT_DOMAIN) + .then() + .statusCode(HttpStatus.OK_200) + .contentType(ContentType.JSON) + .extract() + .body().asString(); + + assertThatJson(retrievedBody).isEqualTo("{\"rules\":[]}"); + + String retrievedBodyDomain2 = when() + .get(DOMAIN_2) + .then() + .statusCode(HttpStatus.OK_200) + .contentType(ContentType.JSON) + .extract() + .body().asString(); + + assertThatJson(retrievedBodyDomain2) + .isEqualTo( + "{\"rules\": [" + + " {" + + " \"id\": \"3\"," + + " \"expression\": \"apache.org\"," + + " \"explanation\": null," + + " \"targetsSender\": true," + + " \"targetsRecipients\": false," + + " \"targetsContent\": false" + + " }" + + "]}"); + } + + @Test + void deleteShouldReturnNotFoundWhenDomainNotInList() { + when() + .delete("strange.com") + .then() + .statusCode(HttpStatus.NOT_FOUND_404) + .contentType(JSON_CONTENT_TYPE) + .body("statusCode", is(HttpStatus.NOT_FOUND_404)) + .body("type", is("InvalidArgument")) + .body("message", is("'strange.com' is not managed by this James server")); + } + + @Test + void deleteShouldReturnBadRequestWhenDomainIsNotValid() { + when() + .delete("[email protected]") + .then() + .statusCode(HttpStatus.BAD_REQUEST_400) + .contentType(JSON_CONTENT_TYPE) + .body("statusCode", is(HttpStatus.BAD_REQUEST_400)) + .body("type", is("InvalidArgument")) + .body("message", is("Invalid request for domain: [email protected]")); + } + } + + @Nested + class DefineList { + + @Test + void getShouldReturnOK() { + dlpStore.store(SENDER_DOMAIN, CONFIGURATION_ITEMS); + + when() + .get(DEFAULT_DOMAIN) + .then() + .statusCode(HttpStatus.OK_200) + .contentType(ContentType.JSON); + } + + @Test + void getShouldReturnABody() { + dlpStore.store(SENDER_DOMAIN, CONFIGURATION_ITEMS); + + String body = when() + .get(DEFAULT_DOMAIN) + .then() + .statusCode(HttpStatus.OK_200) + .contentType(ContentType.JSON) + .extract() + .body() + .asString(); + + assertThatJson(body).isEqualTo( + "{\"rules\": [" + + " {" + + " \"id\": \"1\"," + + " \"expression\": \"james.org\"," + + " \"explanation\": \"explanation 1\"," + + " \"targetsSender\": true," + + " \"targetsRecipients\": true," + + " \"targetsContent\": true" + + " }," + + " {" + + " \"id\": \"2\"," + + " \"expression\": \"james.org\"," + + " \"explanation\": null," + + " \"targetsSender\": true," + + " \"targetsRecipients\": false," + + " \"targetsContent\": false" + + " }" + + "]}"); + } + + @Test + void getShouldReturnAnEmptyBodyWhenDLPStoreIsEmpty() { + dlpStore.store(SENDER_DOMAIN, ImmutableList.of()); + + String body = when() + .get(DEFAULT_DOMAIN) + .then() + .statusCode(HttpStatus.OK_200) + .contentType(ContentType.JSON) + .extract() + .body() + .asString(); + + assertThatJson(body).isEqualTo("{\"rules\":[]}"); + } + + @Test + void getShouldReturnOnlyConfigurationsFromCorrespondingDomain() { + dlpStore.store(SENDER_DOMAIN, CONFIGURATION_ITEMS); + dlpStore.store(SENDER_DOMAIN_2, CONFIGURATION_ITEMS_FOR_DOMAIN_2); + + String body = when() + .get(DEFAULT_DOMAIN) + .then() + .statusCode(HttpStatus.OK_200) + .contentType(ContentType.JSON) + .extract() + .body() + .asString(); + + assertThatJson(body).isEqualTo( + "{\"rules\": [" + + " {" + + " \"id\": \"1\"," + + " \"expression\": \"james.org\"," + + " \"explanation\": \"explanation 1\"," + + " \"targetsSender\": true," + + " \"targetsRecipients\": true," + + " \"targetsContent\": true" + + " }," + + " {" + + " \"id\": \"2\"," + + " \"expression\": \"james.org\"," + + " \"explanation\": null," + + " \"targetsSender\": true," + + " \"targetsRecipients\": false," + + " \"targetsContent\": false" + + " }" + + "]}"); + } + + @Test + void getShouldReturnNotFoundWhenDomainNotInList() { + when() + .get("strange.com") + .then() + .statusCode(HttpStatus.NOT_FOUND_404) + .contentType(JSON_CONTENT_TYPE) + .body("statusCode", is(HttpStatus.NOT_FOUND_404)) + .body("type", is("InvalidArgument")) + .body("message", is("'strange.com' is not managed by this James server")); + } + + @Test + void getShouldReturnBadRequestWhenDomainIsNotValid() { + when() + .get("[email protected]") + .then() + .statusCode(HttpStatus.BAD_REQUEST_400) + .contentType(JSON_CONTENT_TYPE) + .body("statusCode", is(HttpStatus.BAD_REQUEST_400)) + .body("type", is("InvalidArgument")) + .body("message", is("Invalid request for domain: [email protected]")); + } + } +} \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
