Repository: gisaia/ARLAS-server (language: python)

Issue:
Empty column filter should forbid all collections
Today, a collection filter that is null or empty ("" string) allows all collections.
Now we want that:
- if null (no column-filter provided), then all collections are allowed
- if empty (column-filter is provided but is empty string), then all collections are forbidden
- else use its value


Discussion / hints:

Fix patch:
diff --git a/arlas-commons/src/main/java/io/arlas/filter/core/IdentityParam.java b/arlas-commons/src/main/java/io/arlas/filter/core/IdentityParam.java
new file mode 100644
index 000000000..12d278306
--- /dev/null
+++ b/arlas-commons/src/main/java/io/arlas/filter/core/IdentityParam.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to Gisaïa under one or more contributor
+ * license agreements. See the NOTICE.txt file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Gisaïa 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 io.arlas.filter.core;
+
+import io.arlas.commons.config.ArlasAuthConfiguration;
+import io.arlas.filter.config.TechnicalRoles;
+
+import javax.ws.rs.core.HttpHeaders;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+public class IdentityParam {
+
+    public String userId;
+    public String organisation;
+    public List<String> groups;
+
+    public IdentityParam(ArlasAuthConfiguration configuration, HttpHeaders headers) {
+        this.userId = Optional.ofNullable(headers.getHeaderString(configuration.headerUser))
+                .orElse(configuration.anonymousValue);
+
+        // in a context where resources are publicly available, no organisation is defined
+        this.organisation = Optional.ofNullable(headers.getHeaderString(configuration.headerOrganisation))
+                .orElse(PolicyEnforcer.NO_ORG);
+
+        this.groups = Arrays.stream(Optional.ofNullable(headers.getHeaderString(configuration.headerGroup))
+                        .orElse(TechnicalRoles.GROUP_PUBLIC)
+                        .split(","))
+                .map(String::trim)
+                .toList();
+    }
+
+}
diff --git a/arlas-core/src/main/java/io/arlas/server/core/app/ArlasServerConfiguration.java b/arlas-core/src/main/java/io/arlas/server/core/app/ArlasServerConfiguration.java
index 0c0b8e693..73e5f5a83 100644
--- a/arlas-core/src/main/java/io/arlas/server/core/app/ArlasServerConfiguration.java
+++ b/arlas-core/src/main/java/io/arlas/server/core/app/ArlasServerConfiguration.java
@@ -81,9 +81,6 @@ public class ArlasServerConfiguration extends ArlasBaseConfiguration {
     @JsonProperty("collection-auto-discover")
     public CollectionAutoDiscoverConfiguration collectionAutoDiscoverConfiguration;
 
-    @JsonProperty("arlas_auth_column_filter_required")
-    public Boolean arlasAuthColumnFilterRequired;
-
     @JsonProperty("arlas_default_org")
     public String arlasDefaultOrg;
 
diff --git a/arlas-core/src/main/java/io/arlas/server/core/model/CollectionReference.java b/arlas-core/src/main/java/io/arlas/server/core/model/CollectionReference.java
index 25108ad64..e02882d26 100644
--- a/arlas-core/src/main/java/io/arlas/server/core/model/CollectionReference.java
+++ b/arlas-core/src/main/java/io/arlas/server/core/model/CollectionReference.java
@@ -49,7 +49,7 @@ public class CollectionReference implements Serializable {
     public static final String DUBLIN_CORE_LANGUAGE = "language";
 
 
-    @JsonProperty(value = "collection_name", required = true)
+    @JsonProperty(value = COLLECTION_NAME, required = true)
     public String collectionName;
 
     @JsonProperty(value = "params", required = true)
diff --git a/arlas-core/src/main/java/io/arlas/server/core/model/CollectionReferenceParameters.java b/arlas-core/src/main/java/io/arlas/server/core/model/CollectionReferenceParameters.java
index f5358f9bd..6c5371884 100644
--- a/arlas-core/src/main/java/io/arlas/server/core/model/CollectionReferenceParameters.java
+++ b/arlas-core/src/main/java/io/arlas/server/core/model/CollectionReferenceParameters.java
@@ -36,29 +36,29 @@ public class CollectionReferenceParameters implements Serializable {
     private static final long serialVersionUID = 7530916591832941958L;
 
     @NotEmpty
-    @JsonProperty(value = "index_name", required = true)
+    @JsonProperty(value = CollectionReference.INDEX_NAME, required = true)
     public String indexName;
 
     @NotEmpty
-    @JsonProperty(value = "id_path", required = true)
+    @JsonProperty(value = CollectionReference.ID_PATH, required = true)
     public String idPath;
 
     @NotEmpty
-    @JsonProperty(value = "geometry_path", required = true)
+    @JsonProperty(value = CollectionReference.GEOMETRY_PATH, required = true)
     public String geometryPath;
 
     @NotEmpty
-    @JsonProperty(value = "centroid_path", required = true)
+    @JsonProperty(value = CollectionReference.CENTROID_PATH, required = true)
     public String centroidPath;
 
-    @JsonProperty(value = "h3_path")
+    @JsonProperty(value = CollectionReference.H3_PATH)
     public String h3Path;
 
     @NotEmpty
-    @JsonProperty(value = "timestamp_path", required = true)
+    @JsonProperty(value = CollectionReference.TIMESTAMP_PATH, required = true)
     public String timestampPath;
 
-    @JsonProperty(value = "exclude_fields")
+    @JsonProperty(value = CollectionReference.EXCLUDE_FIELDS)
     public String excludeFields = null;
 
     @JsonProperty(value = "update_max_hits")
@@ -67,13 +67,13 @@ public class CollectionReferenceParameters implements Serializable {
     @JsonProperty(value = "taggable_fields")
     public String taggableFields = null;
 
-    @JsonProperty(value = "exclude_wfs_fields")
+    @JsonProperty(value = CollectionReference.EXCLUDE_WFS_FIELDS)
     public String excludeWfsFields = null;
 
     @JsonProperty(value = "custom_params")
     public Map<String, String> customParams = null;
 
-    @JsonProperty(value = "display_names")
+    @JsonProperty(value = CollectionReference.DISPLAY_NAMES)
     public CollectionDisplayNames collectionDisplayNames = null;
 
     @JsonProperty(value = "atom_feed")
@@ -82,10 +82,10 @@ public class CollectionReferenceParameters implements Serializable {
     @JsonProperty(value = "open_search")
     public OpenSearch openSearch = null;
 
-    @JsonProperty(value = "inspire")
+    @JsonProperty(value = CollectionReference.INSPIRE_PATH)
     public Inspire inspire = new Inspire();
 
-    @JsonProperty(value = "dublin_core_element_name")
+    @JsonProperty(value = CollectionReference.DUBLIN_CORE_PATH)
     public DublinCoreElementName dublinCoreElementName = new DublinCoreElementName();
 
     @JsonProperty(value = "raster_tile_url")
diff --git a/arlas-core/src/main/java/io/arlas/server/core/services/CollectionReferenceService.java b/arlas-core/src/main/java/io/arlas/server/core/services/CollectionReferenceService.java
index 0ac9b6614..585453533 100644
--- a/arlas-core/src/main/java/io/arlas/server/core/services/CollectionReferenceService.java
+++ b/arlas-core/src/main/java/io/arlas/server/core/services/CollectionReferenceService.java
@@ -108,12 +108,12 @@ public List<CollectionReferenceDescription> describeAllCollections(List<Collecti
         // Can't use lambdas because of the need to throw the exception of describeCollection()
         List<CollectionReferenceDescription> res  = new ArrayList<>();
         for (CollectionReference collection : collectionReferenceList) {
-            if (!ColumnFilterUtil.cleanColumnFilter(columnFilter).isPresent()
+            if (ColumnFilterUtil.cleanColumnFilter(columnFilter).isEmpty()
                     || ColumnFilterUtil.getCollectionRelatedColumnFilter(columnFilter,collection).isPresent()) {
-                try{
+                try {
                     CollectionReferenceDescription describe = describeCollection(collection, columnFilter);
                     res.add(describe);
-                }catch (ArlasException e){}
+                } catch (ArlasException e) { }
             }
         }
         return res;
diff --git a/arlas-core/src/main/java/io/arlas/server/core/services/ExploreService.java b/arlas-core/src/main/java/io/arlas/server/core/services/ExploreService.java
index a62785d83..7c27b5550 100644
--- a/arlas-core/src/main/java/io/arlas/server/core/services/ExploreService.java
+++ b/arlas-core/src/main/java/io/arlas/server/core/services/ExploreService.java
@@ -104,9 +104,9 @@ private void flat(Map<List<String>, Object> flat, AggregationResponse element, L
         if (element.hits != null) {
             int i = 0;
             for (Object hit : element.hits) {
-                Map flatHit = MapExplorer.flat(hit,new MapExplorer.ReduceArrayOnKey(ArlasServerConfiguration.FLATTEN_CHAR), new HashSet<>());
-                for (Object k: flatHit.keySet()) {
-                    addToFlat(flat, newKeyParts(newKeyParts(keyParts, "hits"), i + "" ), k.toString(), flatHit.get(k).toString());
+                Map<String, Object> flatHit = MapExplorer.flat(hit,new MapExplorer.ReduceArrayOnKey(ArlasServerConfiguration.FLATTEN_CHAR), new HashSet<>());
+                for (String k: flatHit.keySet()) {
+                    addToFlat(flat, newKeyParts(newKeyParts(keyParts, "hits"), i + "" ), k, flatHit.get(k).toString());
                 }
                 i++;
             }
@@ -211,7 +211,7 @@ protected void sortPage(Page page, FluidSearchService fluidSearch) throws ArlasE
     protected void applyProjection(Projection projection, FluidSearchService fluidSearch, Optional<String> columnFilter, CollectionReference collectionReference) throws ArlasException {
         if (ColumnFilterUtil.isValidColumnFilterPresent(columnFilter)) {
             String filteredIncludes = ColumnFilterUtil.getFilteredIncludes(columnFilter, projection, collectionReferenceService.getCollectionFields(collectionReference, columnFilter))
-                    .orElse(
+                    .orElseGet(() ->
                             // if filteredIncludes were to be null or an empty string, FluidSearch would then build a bad request
                             String.join(",", ColumnFilterUtil.getCollectionMandatoryPaths(collectionReference)));
             fluidSearch = fluidSearch.include(filteredIncludes);
diff --git a/arlas-core/src/main/java/io/arlas/server/core/utils/ColumnFilterUtil.java b/arlas-core/src/main/java/io/arlas/server/core/utils/ColumnFilterUtil.java
index b22e79ea4..f47129a2d 100644
--- a/arlas-core/src/main/java/io/arlas/server/core/utils/ColumnFilterUtil.java
+++ b/arlas-core/src/main/java/io/arlas/server/core/utils/ColumnFilterUtil.java
@@ -38,39 +38,40 @@
 
 /**
  * Provide functions to check columns against a column filter.
- *
+ * <br/>
  * The principle is:
- * - if the filter is empty, do not filter at all;
+ * - if the filter is null, do not filter at all (Optional.empty)
+ * - if the filter is empty, forbid all collections (Optional.of(""))
  * - if there is a filter, turn it to regexp predicates
  * - append some collection mandatory fields to it
  * - checking a path is then a simple regexp match
  */
 public class ColumnFilterUtil {
-    // if set to true, a column-filter must be provided to access any collection
-    public static boolean COLUMN_FILTER_REQUIRED = false; // false is legacy behaviour
 
     static Logger LOGGER = LoggerFactory.getLogger(ColumnFilterUtil.class);
 
     //extract fields from request but without the `excludes`
-    private static final Set<String> REQUEST_FIELDS_EXTRACTOR_INCLUDE = RequestFieldsExtractor.INCLUDE_ALL.stream().filter(i -> !i.equalsIgnoreCase(RequestFieldsExtractor.INCLUDE_SEARCH_EXCLUDE)).collect(Collectors.toSet());
+    private static final Set<String> REQUEST_FIELDS_EXTRACTOR_INCLUDE =
+            RequestFieldsExtractor.INCLUDE_ALL
+                    .stream()
+                    .filter(i -> !i.equalsIgnoreCase(RequestFieldsExtractor.INCLUDE_SEARCH_EXCLUDE))
+                    .collect(Collectors.toSet());
 
     /**
      * Check that there aren't forbidden fields into the requests, and that fields are compatible with FGA prerequisites.
      * Also check that the target collection is allowed (at least one column of the collection is available, or
      * there is at least one column - in the column filter - that is not related to a specific collection).
-     * @param columnFilter
-     * @param collectionReference
-     * @param basicRequest
+     * @param columnFilter the value of the colum filter
+     * @param collectionReference the collection to be checked
+     * @param basicRequest the original request in order to check query parameters
      * @throws InternalServerErrorException if fields cannot be extracted from requests
      * @throws ColumnUnavailableException if a field is forbidden
      * @throws CollectionUnavailableException if the target collection is forbidden
-     * @return
      */
     public static void assertRequestAllowed(Optional<String> columnFilter,
                                             CollectionReference collectionReference,
                                             Request basicRequest)
             throws InternalServerErrorException, ColumnUnavailableException, CollectionUnavailableException {
-        LOGGER.debug("Column-Filter value: " + columnFilter.orElse("null"));
         assertRequestAllowed(columnFilter, collectionReference, basicRequest, null);
     }
 
@@ -79,30 +80,30 @@ public static void assertRequestAllowed(Optional<String> columnFilter,
                                             Request basicRequest,
                                             RequestFieldsExtractor.IRequestFieldsExtractor requestExtractor)
             throws InternalServerErrorException, ColumnUnavailableException, CollectionUnavailableException {
-
+        LOGGER.debug("Column-Filter value: " + columnFilter.orElse("null"));
         Optional<String> cleanColumnFilter = cleanColumnFilter(columnFilter);
 
-        if (!cleanColumnFilter.isPresent()) {
+        if (cleanColumnFilter.isEmpty()) {
             return;
         }
 
         Optional<String> collectionColumnFilter = getCollectionRelatedColumnFilter(cleanColumnFilter, collectionReference);
-        if (!collectionColumnFilter.isPresent()) {
+        if (collectionColumnFilter.isEmpty()) {
             throw new CollectionUnavailableException(collectionReference);
         }
 
         Optional<Set<String>> columnFilterPredicates = ColumnFilterUtil.getColumnFilterPredicates(collectionColumnFilter, collectionReference);
 
-        if (!columnFilterPredicates.isPresent()) {
+        if (columnFilterPredicates.isEmpty()) {
             return;
         }
 
         assertQHasCol(basicRequest);
 
         //do not consider user columns with wildcards - they should be checked later against real fields, if necessary
-        Set<String> forbiddenFields = RequestFieldsExtractor.extract(requestExtractor, basicRequest, REQUEST_FIELDS_EXTRACTOR_INCLUDE)
-                .filter(
-                        f -> !f.contains("*") && !FilterMatcherUtil.matches(columnFilterPredicates, f))
+        Set<String> forbiddenFields = RequestFieldsExtractor
+                .extract(requestExtractor, basicRequest, REQUEST_FIELDS_EXTRACTOR_INCLUDE)
+                .filter(f -> !f.contains("*") && !FilterMatcherUtil.matches(columnFilterPredicates, f))
                 .collect(Collectors.toSet());
 
         if (!forbiddenFields.isEmpty()) {
@@ -112,46 +113,56 @@ public static void assertRequestAllowed(Optional<String> columnFilter,
 
     /**
      * Check that the "q" filters have defined target field, which is NOT a wildcard
-     * @param request
+     * @param request the request to be checked
      * @throws ColumnUnavailableException if a "q" has no target field
      */
     private static void assertQHasCol(Request request) throws ColumnUnavailableException {
-        long qWithoutCol = Optional.ofNullable(request.filter).flatMap(filter -> Optional.ofNullable(filter.q)
-                .map(q -> q.stream().flatMap(
-                        qList -> qList.stream().filter(
-                                qFilter -> !qFilter.contains(":") || StringUtils.substringBefore(qFilter, ":").contains("*")))
-                        .count())).orElse(0l);
+        long qWithoutCol = Optional.ofNullable(request.filter)
+                .flatMap(filter -> Optional.ofNullable(filter.q)
+                        .map(q -> q.stream()
+                                .flatMap(qList -> qList.stream()
+                                        .filter(qFilter -> !qFilter.contains(":")
+                                                || StringUtils.substringBefore(qFilter, ":").contains("*")
+                                        )
+                                )
+                                .count()
+                        )
+                )
+                .orElse(0L);
 
         if (qWithoutCol > 0) {
             throw new ColumnUnavailableException("Searching with 'q' parameter without an explicit column is not available");
         }
     }
 
-    public static void assertFieldAvailable(Optional<String> columnFilter, CollectionReference collectionReference, String field) throws ColumnUnavailableException, CollectionUnavailableException {
+    public static void assertFieldAvailable(Optional<String> columnFilter,
+                                            CollectionReference collectionReference,
+                                            String field) throws ColumnUnavailableException, CollectionUnavailableException {
         Optional<Set<String>> columFilterPredicates = getColumnFilterPredicates(columnFilter, collectionReference);
         assertFieldAvailable(columFilterPredicates, field);
     }
 
-    public static void assertFieldAvailable(Optional<Set<String>> columnFilterPredicates, String field) throws ColumnUnavailableException {
-
+    public static void assertFieldAvailable(Optional<Set<String>> columnFilterPredicates,
+                                            String field) throws ColumnUnavailableException {
         if (!FilterMatcherUtil.matches(columnFilterPredicates, field)) {
-            throw new ColumnUnavailableException(new HashSet<>(Arrays.asList(field)));
+            throw new ColumnUnavailableException(new HashSet<>(List.of(field)));
         }
     }
 
     /**
      * Check if an openGisFilter uses only fields allowed in column filter
-     * @param columnFilter
-     * @param collectionDescription
-     * @param openGisFilter
-     * @throws ColumnUnavailableException
+     * @param columnFilter the value of the colum filter
+     * @param collectionDescription the collection to be checked
+     * @param openGisFilter the openGIS filter
+     * @throws ColumnUnavailableException if the filter uses a forbidden field
      */
-    public static void assertOpenGisFilterAllowed(Optional<String> columnFilter, CollectionReferenceDescription collectionDescription,
+    public static void assertOpenGisFilterAllowed(Optional<String> columnFilter,
+                                                  CollectionReferenceDescription collectionDescription,
                                                   List<String> openGisFilter) throws ArlasException {
 
         Optional<String> cleanColumnFilter = ColumnFilterUtil.cleanColumnFilter(columnFilter);
 
-        if (!cleanColumnFilter.isPresent()) {
+        if (cleanColumnFilter.isEmpty()) {
             return;
         }
 
@@ -165,17 +176,18 @@ public static void assertOpenGisFilterAllowed(Optional<String> columnFilter, Col
         }
     }
 
-    public static Optional<String> getFilteredIncludes(Optional<String> columnFilter, Projection projection, Set<String> collectionAllowedFields) {
+    public static Optional<String> getFilteredIncludes(Optional<String> columnFilter,
+                                                       Projection projection,
+                                                       Set<String> collectionAllowedFields) {
 
         if (projection == null || StringUtils.isBlank(projection.includes)) {
-            return Optional.ofNullable(columnFilter.get());
+            return columnFilter;
         }
 
         Optional<Set<String>> includesPredicates = FilterMatcherUtil.filterToPredicatesAsSet(Optional.of(projection.includes));
 
         return collectionAllowedFields.stream()
-                .filter(
-                        f -> FilterMatcherUtil.matches(includesPredicates, f))
+                .filter(f -> FilterMatcherUtil.matches(includesPredicates, f))
                 .reduce((left, right) -> left + "," + right);
     }
 
@@ -189,24 +201,17 @@ public static boolean isValidColumnFilterPresent(Optional<String> columnFilter)
     }
 
     public static Optional<String> cleanColumnFilter(Optional<String> columnFilter) throws CollectionUnavailableException {
-
-        if (COLUMN_FILTER_REQUIRED && (columnFilter == null || columnFilter.isEmpty())) {
-            throw new CollectionUnavailableException("column-filter is mandatory according to ARLAS server configuration");
-        }
-
-        if (columnFilter == null) {
-            LOGGER.warn("column filter is null, Optional.empty() is expected instead");
-            return Optional.empty();
+        if (columnFilter.isPresent() && columnFilter.get().isBlank()) {
+            throw new CollectionUnavailableException("Empty column filter forbids all collections");
         }
-
-        return columnFilter.filter(StringUtils::isNotBlank);
+        return columnFilter;
     }
 
     /**
      * Add collection mandatory fields, but only if a column filter is provided
-     * @param columnFilter
-     * @param collectionReference
-     * @return
+     * @param columnFilter the column filter to be checked
+     * @param collectionReference the collection reference to be analysed
+     * @return the filter as a Set of String
      */
     public static Optional<Set<String>> getColumnFilterPredicates(Optional<String> columnFilter, CollectionReference collectionReference) throws CollectionUnavailableException {
         cleanColumnFilter(columnFilter);
@@ -216,8 +221,7 @@ public static Optional<Set<String>> getColumnFilterPredicates(Optional<String> c
                         cols,
                         getCollectionMandatoryPaths(collectionReference)
                                 .stream()
-                                .map(
-                                        c -> c.replaceAll("\\.", "\\\\.")))
+                                .map(c -> c.replaceAll("\\.", "\\\\.")))
                         .collect(Collectors.toSet()));
     }
 
@@ -232,9 +236,9 @@ public static List<String> getCollectionMandatoryPaths(CollectionReference colle
     /**
      * Return the column filter related to a collection.
      * It DOES NOT include the collection mandatory path.
-     * @param columnFilter
-     * @param collectionReference
-     * @return
+     * @param columnFilter the column filter to be checked
+     * @param collectionReference the collection reference to be analysed
+     * @return the column filter related to the collection
      */
     public static Optional<String> getCollectionRelatedColumnFilter(Optional<String> columnFilter, CollectionReference collectionReference) throws CollectionUnavailableException {
         return cleanColumnFilter(columnFilter)
@@ -263,26 +267,23 @@ private static Function<Stream<String>, Stream<String>> getCollectionFilters(Col
     public static void assertCollectionsAllowed(Optional<String> columnFilter, List<CollectionReference> collections) throws CollectionUnavailableException {
         Optional<String> cleanColumnFilter = cleanColumnFilter(columnFilter);
 
-        if (!cleanColumnFilter.isPresent()) {
+        if (cleanColumnFilter.isEmpty()) {
             return;
         }
 
         for (CollectionReference collection : collections) {
-            if (!getCollectionRelatedColumnFilter(cleanColumnFilter, collection).isPresent()) {
+            if (getCollectionRelatedColumnFilter(cleanColumnFilter, collection).isEmpty()) {
                 throw new CollectionUnavailableException(collection);
             }
         }
     }
 
-    public static Set<String> getAllowedCollections(Optional<String> columnFilter) throws CollectionUnavailableException {
-        if (columnFilter == null || !columnFilter.isPresent()) {
-            if (!COLUMN_FILTER_REQUIRED) {
-                return Collections.singleton("*"); // all collections allowed
-            } else {
-                throw new CollectionUnavailableException("column-filter is mandatory according to ARLAS server configuration");
-            }
+    public static Set<String> getAllowedCollections(Optional<String> columnFilter) {
+        if (columnFilter.isEmpty()) {
+            return Collections.singleton("*"); // all collections allowed
         } else {
             Set<String> res = Arrays.stream(columnFilter.get().split(","))
+                    .filter(cf -> !cf.isBlank())
                     .map(f -> f.contains(":") ? (f.split(":")[0].length() != 0 ? f.split(":")[0] : "*") : "*") // "param" or ":param" or "col:param"
                     .collect(Collectors.toSet());
             if (res.contains("*")) { // if one of the collection is "all" then
diff --git a/arlas-server/src/main/java/io/arlas/server/app/ArlasServer.java b/arlas-server/src/main/java/io/arlas/server/app/ArlasServer.java
index 7ab45f66a..a6c66327d 100644
--- a/arlas-server/src/main/java/io/arlas/server/app/ArlasServer.java
+++ b/arlas-server/src/main/java/io/arlas/server/app/ArlasServer.java
@@ -22,21 +22,20 @@
 import com.fasterxml.jackson.annotation.JsonInclude.Include;
 import com.fasterxml.jackson.databind.DeserializationFeature;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import io.arlas.commons.cache.CacheFactory;
 import io.arlas.commons.config.ArlasConfiguration;
 import io.arlas.commons.config.ArlasCorsConfiguration;
 import io.arlas.commons.exceptions.ArlasExceptionMapper;
 import io.arlas.commons.exceptions.ConstraintViolationExceptionMapper;
 import io.arlas.commons.exceptions.IllegalArgumentExceptionMapper;
 import io.arlas.commons.exceptions.JsonProcessingExceptionMapper;
+import io.arlas.commons.rest.utils.PrettyPrintFilter;
 import io.arlas.filter.core.PolicyEnforcer;
 import io.arlas.server.admin.task.CollectionAutoDiscover;
 import io.arlas.server.core.app.ArlasServerConfiguration;
-import io.arlas.commons.cache.CacheFactory;
 import io.arlas.server.core.managers.CacheManager;
 import io.arlas.server.core.managers.CollectionReferenceManager;
 import io.arlas.server.core.services.ExploreService;
-import io.arlas.commons.rest.utils.PrettyPrintFilter;
-import io.arlas.server.core.utils.ColumnFilterUtil;
 import io.arlas.server.ogc.csw.CSWHandler;
 import io.arlas.server.ogc.csw.CSWService;
 import io.arlas.server.ogc.csw.writer.getrecords.AtomGetRecordsMessageBodyWriter;
@@ -87,7 +86,8 @@
 import javax.servlet.DispatcherType;
 import javax.servlet.FilterRegistration;
 import javax.ws.rs.core.HttpHeaders;
-import java.util.*;
+import java.util.Collections;
+import java.util.EnumSet;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
@@ -121,8 +121,6 @@ public void run(ArlasServerConfiguration configuration, Environment environment)
         configuration.check();
         LOGGER.info("Checked configuration: " + (new ObjectMapper()).writer().writeValueAsString(configuration));
 
-        ColumnFilterUtil.COLUMN_FILTER_REQUIRED = configuration.arlasAuthColumnFilterRequired;
-
         CacheFactory cacheFactory = (CacheFactory) Class
                 .forName(configuration.arlasCacheFactoryClass)
                 .getConstructor(ArlasConfiguration.class)
@@ -253,7 +251,7 @@ public void run(ArlasServerConfiguration configuration, Environment environment)
             ScheduledExecutorServiceBuilder sesBuilder = environment.lifecycle().scheduledExecutorService(nameFormat);
             ScheduledExecutorService ses = sesBuilder.build();
             Runnable autoDiscoverTask = new CollectionAutoDiscover(dbToolFactory.getCollectionReferenceService(), configuration);
-            ses.scheduleWithFixedDelay(autoDiscoverTask, 10, scheduleAutoDiscover, TimeUnit.SECONDS);
+            ses.scheduleWithFixedDelay(autoDiscoverTask, 10L, scheduleAutoDiscover, TimeUnit.SECONDS);
         }
 
         //healthchecks
diff --git a/conf/configuration.yaml b/conf/configuration.yaml
index dd5ec548e..6a3dd7dd3 100644
--- a/conf/configuration.yaml
+++ b/conf/configuration.yaml
@@ -178,7 +178,6 @@ arlas_database_factory_class: ${ARLAS_DATABASE_FACTORY_CLASS:-io.arlas.server.im
 ############ AUTHENTICATION              ###############
 ########################################################
 arlas_auth_policy_class: ${ARLAS_AUTH_POLICY_CLASS:-io.arlas.filter.impl.NoPolicyEnforcer}
-arlas_auth_column_filter_required: ${ARLAS_AUTH_COLUMN_FILTER_REQUIRED:-false}
 arlas_default_org: ${ARLAS_DEFAULT_ORG:-arlas}
 
 arlas_auth:
diff --git a/docker-compose.yml b/docker-compose.yml
index e1253d1c7..8e3cfcfb0 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -23,7 +23,6 @@ services:
       - ARLAS_AUTH_PERMISSION_URL="${ARLAS_AUTH_PERMISSION_URL:-http://arlas-iam-server:9990/arlas_iam_server/permissions}"
       - ARLAS_CORS_ENABLED="${ARLAS_CORS_ENABLED:-false}"
       - ARLAS_AUTH_CERT_URL="${ARLAS_AUTH_CERT_URL}"
-      - ARLAS_AUTH_COLUMN_FILTER_REQUIRED="${ARLAS_AUTH_COLUMN_FILTER_REQUIRED:-false}"
       - ARLAS_CACHE_FACTORY_CLASS="${ARLAS_CACHE_FACTORY_CLASS:-io.arlas.server.core.impl.cache.HazelcastCacheFactory}"
       - ARLAS_CACHE_TIMEOUT="${ARLAS_CACHE_TIMEOUT:-5}"
       - ARLAS_SWAGGER_RESOURCE_PKG="${ARLAS_SWAGGER_RESOURCE_PKG:-io.arlas.server.rest}"
diff --git a/docs/arlas-server-configuration.md b/docs/arlas-server-configuration.md
index e52af9016..99fe293c8 100644
--- a/docs/arlas-server-configuration.md
+++ b/docs/arlas-server-configuration.md
@@ -64,8 +64,6 @@ docker run -ti -d \
 | Environment variable              | ARLAS Server configuration variable | Default                               | Description                                                                          |
 |-----------------------------------|-------------------------------------|---------------------------------------|--------------------------------------------------------------------------------------|
 | ARLAS_AUTH_POLICY_CLASS           | arlas_auth_policy_class             | io.arlas.filter.impl.NoPolicyEnforcer | Specify a PolicyEnforcer class to load in order to activate Authentication if needed |
-| ARLAS_AUTH_COLUMN_FILTER_REQUIRED | arlas_auth_column_filter_required   | false                                 | Specifies if a column filter is required (true) or not (false)                       |
-
 
 ### CORS, HEADERS for API response
 
diff --git a/pom.xml b/pom.xml
index 7cacd9cad..e9b354d6b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -193,6 +193,25 @@
           </config>
         </configuration>
       </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-site-plugin</artifactId>
+        <version>4.0.0-M4</version>
+      </plugin>
+
+      <plugin>
+        <groupId>com.github.spotbugs</groupId>
+        <artifactId>spotbugs-maven-plugin</artifactId>
+        <version>4.7.3.0</version>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-checkstyle-plugin</artifactId>
+        <version>3.2.0</version>
+      </plugin>
+
     </plugins>
   </build>
 
@@ -234,4 +253,36 @@
       <url>https://jcenter.bintray.com</url>
     </repository>
   </repositories>
+
+  <reporting>
+    <plugins>
+      <plugin>
+        <groupId>com.github.spotbugs</groupId>
+        <artifactId>spotbugs-maven-plugin</artifactId>
+        <version>4.7.3.0</version>
+        <configuration>
+          <!-- Optional directory to put spotbugs xml report -->
+        </configuration>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-pmd-plugin</artifactId>
+        <version>3.19.0</version>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-checkstyle-plugin</artifactId>
+        <version>3.2.0</version>
+        <reportSets>
+          <reportSet>
+            <reports>
+              <report>checkstyle</report>
+            </reports>
+          </reportSet>
+        </reportSets>
+      </plugin>
+    </plugins>
+  </reporting>
 </project>
\ No newline at end of file


Test patch:
diff --git a/arlas-tests/src/test/java/io/arlas/server/tests/ogc/csw/CSWServiceIT.java b/arlas-tests/src/test/java/io/arlas/server/tests/ogc/csw/CSWServiceIT.java
index a52ec21cf..42716f0c9 100644
--- a/arlas-tests/src/test/java/io/arlas/server/tests/ogc/csw/CSWServiceIT.java
+++ b/arlas-tests/src/test/java/io/arlas/server/tests/ogc/csw/CSWServiceIT.java
@@ -73,7 +73,7 @@ public void testGetCapabilities() throws Exception {
 
     @Test
     public void testGetCapabilitiesWithColumnFilter() throws Exception {
-        handleGetCapabilities(get("GetCapabilities", new ArrayList<>(), Optional.of(" ")));
+        handleGetCapabilities(get("GetCapabilities", new ArrayList<>(), Optional.empty()));
         handleGetCapabilities(get("GetCapabilities", new ArrayList<>(), Optional.of("anyFieldFromAnyCollection")));
         handleGetCapabilitiesEmpty(get("GetCapabilities", new ArrayList<>(), Optional.of("unknownCollection:anyField")));
 
@@ -90,7 +90,7 @@ public void testGetRecords() throws Exception {
     public void testGetRecordsWithColumnFilter() throws Exception {
         String filteredCollection = dcelements[0].title.split(" ")[0].toLowerCase();
 
-        handleGetRecords(get("GetRecords", new ArrayList<>(), Optional.of(" ")));
+        handleGetRecords(get("GetRecords", new ArrayList<>(), Optional.empty()));
         handleGetRecords(get("GetRecords", new ArrayList<>(), Optional.of("anyFieldFromAnyCollection")));
         handleGetRecordsEmpty(get("GetRecords", new ArrayList<>(), Optional.of("unknownCollection:anyField")));
 
@@ -115,8 +115,10 @@ public void testGetRecordByIdWithCollectionBasedColumnFilter() throws Exception
         String id = getRecordId(collectionName);
 
         handleGetRecordById(
-                get("GetRecordById", Arrays.asList(new ImmutablePair<>("ID", id)), Optional.of(" ")),
+                get("GetRecordById", Arrays.asList(new ImmutablePair<>("ID", id)), Optional.empty()),
                 collectionName);
+        handleUnavailableCollection(
+                get("GetRecordById", Arrays.asList(new ImmutablePair<>("ID", id)), Optional.of("")));
         handleGetRecordById(
                 get("GetRecordById", Arrays.asList(new ImmutablePair<>("ID", id)), Optional.of("panda:anyField")),
                 collectionName);
diff --git a/arlas-tests/src/test/java/io/arlas/server/tests/ogc/wfs/WFSServiceIT.java b/arlas-tests/src/test/java/io/arlas/server/tests/ogc/wfs/WFSServiceIT.java
index 121416f72..c330c1584 100644
--- a/arlas-tests/src/test/java/io/arlas/server/tests/ogc/wfs/WFSServiceIT.java
+++ b/arlas-tests/src/test/java/io/arlas/server/tests/ogc/wfs/WFSServiceIT.java
@@ -62,7 +62,6 @@ public void testGetFeatureNoHeaderFilter() throws Exception {
 
     @Test
     public void testGetFeatureWithAvailableColumn() throws Exception {
-        //an empty column filter is not considered
         handleGetFeatureNoHeaderFilter(
                 get(Arrays.asList(
                         new ImmutablePair<>("SERVICE", "WFS"),
@@ -70,7 +69,7 @@ public void testGetFeatureWithAvailableColumn() throws Exception {
                         new ImmutablePair<>("COUNT", "1000"),
                         new ImmutablePair<>("REQUEST", "GetFeature")),
                         new Filter(),
-                        Optional.of("")));
+                        Optional.empty()));
 
         handleGetFeatureNoHeaderFilter(
                 get(Arrays.asList(
@@ -131,6 +130,15 @@ public void testGetFeatureWithCollectionBasedColumnFiltering() throws Exception
                         new ImmutablePair<>("REQUEST", "GetFeature")),
                         new Filter(),
                         Optional.of("notExisting:params")));
+
+        handleUnavailableCollection(
+                get(Arrays.asList(
+                        new ImmutablePair<>("SERVICE", "WFS"),
+                        new ImmutablePair<>("VERSION", "2.0.0"),
+                        new ImmutablePair<>("COUNT", "1000"),
+                        new ImmutablePair<>("REQUEST", "GetFeature")),
+                        new Filter(),
+                        Optional.of("")));
     }
 
     @Test
@@ -166,7 +174,7 @@ public void testGetPropertyValueWithAvailableColumn() throws Exception {
                 new ImmutablePair<>("REQUEST", "GetPropertyValue"),
                 new ImmutablePair<>("valuereference", "params.job")),
                 new Filter(),
-                Optional.of("")));
+                Optional.empty()));
 
         handleGetPropertyValueNoHeaderFilter(get(Arrays.asList(
                 new ImmutablePair<>("SERVICE", "WFS"),
@@ -265,7 +273,7 @@ public void testDescribeFeatureWithAvailableColumn() throws Exception {
                         new ImmutablePair<>("VERSION", "2.0.0"),
                         new ImmutablePair<>("REQUEST", "DescribeFeatureType")),
                         new Filter(),
-                        Optional.of("")));
+                        Optional.empty()));
 
         handleDescribeFeature(
                 get(Arrays.asList(
diff --git a/arlas-tests/src/test/java/io/arlas/server/tests/ogc/wfs/WFSServiceWithOgcFilterIT.java b/arlas-tests/src/test/java/io/arlas/server/tests/ogc/wfs/WFSServiceWithOgcFilterIT.java
index acbb48a4a..61ddcb928 100644
--- a/arlas-tests/src/test/java/io/arlas/server/tests/ogc/wfs/WFSServiceWithOgcFilterIT.java
+++ b/arlas-tests/src/test/java/io/arlas/server/tests/ogc/wfs/WFSServiceWithOgcFilterIT.java
@@ -50,7 +50,7 @@ public void testGetFeatureFilterWithEmptyColumnFilter() throws Exception {
         handleOK(
                 get(getFeatureParams(),
                         new Filter(),
-                        Optional.of(" "))
+                        Optional.empty())
         );
     }
 
@@ -107,7 +107,7 @@ public void testGetPropertyValueFilterWithEmptyColumnFilter() throws Exception {
         handleOK(
                 get(getGetPropertyValueParams(),
                         new Filter(),
-                        Optional.of(" "))
+                        Optional.empty())
         );
     }
 
diff --git a/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/AbstractComputationTest.java b/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/AbstractComputationTest.java
index 4d8d2c6bc..9a799520b 100644
--- a/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/AbstractComputationTest.java
+++ b/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/AbstractComputationTest.java
@@ -50,8 +50,8 @@ public void testComputeRequest() throws Exception {
         /** AVG **/
         computationRequest.field = "params.age";
         computationRequest.metric = ComputationEnum.AVG;
-        handleComputationRequest(post(computationRequest, ""), 595, 3702.8571428571427f);//empty column filter is not considered
-        handleComputationRequest(get(computationRequest.field, computationRequest.metric.value(), ""), 595, 3702.8571428571427f);//empty column filter is not considered
+        handleComputationRequest(post(computationRequest), 595, 3702.8571428571427f);
+        handleComputationRequest(get(computationRequest.field, computationRequest.metric.value()), 595, 3702.8571428571427f);
 
         /** CARDINALITY **/
         computationRequest.field = "params.job";
diff --git a/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/AbstractDescribeTest.java b/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/AbstractDescribeTest.java
index 313906a61..78248cdd1 100644
--- a/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/AbstractDescribeTest.java
+++ b/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/AbstractDescribeTest.java
@@ -45,7 +45,7 @@ public void testDescribeFeature() throws Exception {
 
     @Test
     public void testDescribeFeatureWithEmptyColumFilter() throws Exception {
-        handleMatchingResponse(get(Optional.of("")), new JsonPath(this.getClass().getClassLoader().getResourceAsStream(getDescribeResultPath())));
+        handleMatchingResponse(get(Optional.empty()), new JsonPath(this.getClass().getClassLoader().getResourceAsStream(getDescribeResultPath())));
     }
 
     @Test
@@ -104,11 +104,18 @@ public void testDescribeFeatureWithSpecificColumFilter() throws Exception {
     protected abstract void handleNotMatchingResponse(ValidatableResponse response, JsonPath jsonPath);
 
     protected ValidatableResponse get(Optional<String> columnFilter) {
-        return given()
-                .header(COLUMN_FILTER, columnFilter.orElse(""))
-                .when()
-                .get(getUrlPath("geodata"))
-                .then();
+        if (columnFilter.isEmpty()) {
+            return given()
+                    .when()
+                    .get(getUrlPath("geodata"))
+                    .then();
+        } else {
+            return given()
+                    .header(COLUMN_FILTER, columnFilter.get())
+                    .when()
+                    .get(getUrlPath("geodata"))
+                    .then();
+        }
     }
 
     /**
diff --git a/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/AbstractFilteredTest.java b/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/AbstractFilteredTest.java
index 1822d6618..f9b64357f 100644
--- a/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/AbstractFilteredTest.java
+++ b/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/AbstractFilteredTest.java
@@ -135,11 +135,13 @@ public void testQueryFilter() throws Exception {
         handleMatchingQueryFilter(get("q", request.filter.q.get(0).get(0)), 595);
         handleMatchingQueryFilter(header(request.filter), 595);
 
-        //an empty column filter is not considered
         request.filter.q = Arrays.asList(new MultiValueFilter<>("fullname:My name:is"));
-        handleNotMatchingQueryFilter(post(request, COLUMN_FILTER, ""));
-        handleNotMatchingQueryFilter(get("q", request.filter.q.get(0).get(0), COLUMN_FILTER, ""));
-        handleNotMatchingQueryFilter(header(request.filter, COLUMN_FILTER, ""));
+        handleNotMatchingQueryFilter(post(request));
+        handleNotMatchingQueryFilter(get("q", request.filter.q.get(0).get(0)));
+        handleNotMatchingQueryFilter(header(request.filter));
+        handleUnavailableCollection(post(request, COLUMN_FILTER, ""));
+        handleUnavailableCollection(get("q", request.filter.q.get(0).get(0), COLUMN_FILTER, ""));
+        handleUnavailableCollection(header(request.filter, COLUMN_FILTER, ""));
 
         request.filter.q = Arrays.asList(new MultiValueFilter<>("fullname:My name is"));
         handleMatchingQueryFilter(post(request, COLUMN_FILTER, "fullname*"), 595);
@@ -240,9 +242,11 @@ public void testRangeFilter() throws Exception {
     public void testPwithinFilter() throws Exception {
         /** west < east bbox */
         request.filter.f = Arrays.asList(new MultiValueFilter<>(new Expression("geo_params.centroid", OperatorEnum.within, "-5,-5,5,5")));
-        handleMatchingGeometryFilter(post(request, COLUMN_FILTER, ""), 1, everyItem(equalTo("0,0")));//an empty column filter is not considered
-        handleMatchingGeometryFilter(get("f", request.filter.f.get(0).get(0).toString(), COLUMN_FILTER, ""), 1, everyItem(equalTo("0,0")));//an empty column filter is not considered
+        handleMatchingGeometryFilter(post(request), 1, everyItem(equalTo("0,0")));
+        handleMatchingGeometryFilter(get("f", request.filter.f.get(0).get(0).toString()), 1, everyItem(equalTo("0,0")));
         handleMatchingGeometryFilter(header(request.filter), 1, everyItem(equalTo("0,0")));
+        handleUnavailableCollection(post(request, COLUMN_FILTER, ""));
+        handleUnavailableCollection(get("f", request.filter.f.get(0).get(0).toString(), COLUMN_FILTER, ""));
 
         /** clock-wise WKT */
         request.filter.f = Arrays.asList(new MultiValueFilter<>(new Expression("geo_params.centroid", OperatorEnum.within, "POLYGON((-5 -5, -5 5, 5 5, 5 -5, -5 -5))")));
@@ -1693,14 +1697,25 @@ public void testMixedFilter() throws Exception {
         handleNotMatchingRequest(
                 givenFilterableRequestParams()
                         .header(PARTITION_FILTER, objectMapper.writeValueAsString(filterHeader))
-                        .header(COLUMN_FILTER, "") //an empty column filter is not considered
                         .param("f", (new Expression("params.job", OperatorEnum.eq, "Architect")).toString())
                         .when().get(getUrlPath("geodata"))
                         .then());
         handleNotMatchingRequest(
                 givenFilterableRequestBody().body(request)
                         .header(PARTITION_FILTER, objectMapper.writeValueAsString(filterHeader))
-                        .header(COLUMN_FILTER, "")//an empty column filter is not considered
+                        .when().post(getUrlPath("geodata"))
+                        .then());
+        handleUnavailableCollection(
+                givenFilterableRequestParams()
+                        .header(PARTITION_FILTER, objectMapper.writeValueAsString(filterHeader))
+                        .header(COLUMN_FILTER, "")
+                        .param("f", (new Expression("params.job", OperatorEnum.eq, "Architect")).toString())
+                        .when().get(getUrlPath("geodata"))
+                        .then());
+        handleUnavailableCollection(
+                givenFilterableRequestBody().body(request)
+                        .header(PARTITION_FILTER, objectMapper.writeValueAsString(filterHeader))
+                        .header(COLUMN_FILTER, "")
                         .when().post(getUrlPath("geodata"))
                         .then());
         request.filter = new Filter();
@@ -1754,7 +1769,13 @@ public void testMixedFilterMultiCollectionFormat() throws Exception {
         handleNotMatchingRequest(
                 givenFilterableRequestParams()
                         .header(PARTITION_FILTER, objectMapper.writeValueAsString(filterHeader))
-                        .header(COLUMN_FILTER, "") //an empty column filter is not considered
+                        .param("f", (new Expression("params.job", OperatorEnum.eq, "Architect")).toString())
+                        .when().get(getUrlPath("geodata"))
+                        .then());
+        handleUnavailableCollection(
+                givenFilterableRequestParams()
+                        .header(PARTITION_FILTER, objectMapper.writeValueAsString(filterHeader))
+                        .header(COLUMN_FILTER, "")
                         .param("f", (new Expression("params.job", OperatorEnum.eq, "Architect")).toString())
                         .when().get(getUrlPath("geodata"))
                         .then());
@@ -1762,7 +1783,12 @@ public void testMixedFilterMultiCollectionFormat() throws Exception {
         handleNotMatchingRequest(
                 givenFilterableRequestBody().body(request)
                         .header(PARTITION_FILTER, objectMapper.writeValueAsString(filterHeader))
-                        .header(COLUMN_FILTER, "")//an empty column filter is not considered
+                        .when().post(getUrlPath("geodata"))
+                        .then());
+        handleUnavailableCollection(
+                givenFilterableRequestBody().body(request)
+                        .header(PARTITION_FILTER, objectMapper.writeValueAsString(filterHeader))
+                        .header(COLUMN_FILTER, "")
                         .when().post(getUrlPath("geodata"))
                         .then());
         request.filter = new Filter();
@@ -2261,7 +2287,9 @@ private ValidatableResponse post(Request request) {
                 .when().post(getUrlPath("geodata"))
                 .then();
     }
-    protected Request handlePostRequest(Request req){return req;}
+    protected Request handlePostRequest(Request req) {
+        return req;
+    }
 
     private ValidatableResponse post(Request request, String headerkey, String headerValue) {
         return givenFilterableRequestBody().body(handlePostRequest(request))
diff --git a/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/AbstractPaginatedTest.java b/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/AbstractPaginatedTest.java
index d240305eb..d9ca0bea3 100644
--- a/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/AbstractPaginatedTest.java
+++ b/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/AbstractPaginatedTest.java
@@ -35,6 +35,7 @@
 import static org.hamcrest.Matchers.notNullValue;
 import java.net.URLDecoder;
 import java.util.HashMap;
+import java.util.Optional;
 
 public abstract class AbstractPaginatedTest extends AbstractFormattedTest{
     protected static Search search = new Search();
@@ -96,9 +97,10 @@ public void testPageSort() throws Exception {
         search.filter = new Filter();
         search.filter.righthand = false;
         search.page.sort = "-params.job";
-        //an empty column filter is not considered
-        handleSortParameter(post(search, ""), "Dancer");
-        handleSortParameter(get("sort", search.page.sort, ""), "Dancer");
+        handleSortParameter(post(search), "Dancer");
+        handleSortParameter(get("sort", search.page.sort), "Dancer");
+        handleUnavailableCollection(post(search, ""));
+        handleUnavailableCollection(get("sort", search.page.sort, ""));
 
         //geodistance is never filtered
         search.page.sort = "geodistance:-50 -110";
@@ -576,28 +578,32 @@ public void testSortWithSearchAfterWrongLastValue() throws Exception {
     //---------------------- ValidatableResponse ------------------
     //----------------------------------------------------------------
     protected ValidatableResponse get(String param, Object paramValue) {
-        return get(param, paramValue, "");
+        return get(param, paramValue, null);
     }
     protected ValidatableResponse get(String param, Object paramValue, String columnFilter) {
-        return givenFilterableRequestParams().param(param, paramValue)
-                .header(COLUMN_FILTER, columnFilter)
-                .when().get(getUrlPath("geodata"))
-                .then();
+        if (columnFilter == null) {
+            return givenFilterableRequestParams().param(param, paramValue)
+                    .when().get(getUrlPath("geodata"))
+                    .then();
+        } else {
+            return givenFilterableRequestParams().param(param, paramValue)
+                    .header(COLUMN_FILTER, columnFilter)
+                    .when().get(getUrlPath("geodata"))
+                    .then();
+        }
     }
 
     protected ValidatableResponse get(String param1, Object paramValue1, String param2, Object paramValue2) {
-        return get(param1, paramValue1, param2, paramValue2, "");
-    }
-
-    protected ValidatableResponse get(String param1, Object paramValue1, String param2, Object paramValue2, String columnFilter) {
         return givenFilterableRequestBody().param(param1, paramValue1).param(param2, paramValue2)
-                .header(COLUMN_FILTER, columnFilter)
                 .when().get(getUrlPath("geodata"))
                 .then();
     }
 
     private ValidatableResponse post(Request request) {
-        return post(request, "");
+        RequestSpecification req = givenBigSizedRequestParamsPost();
+        return req.body(handlePostRequest(request))
+                .when().post(getUrlPath("geodata"))
+                .then();
     }
 
     private ValidatableResponse post(Request request, String columnsFilter) {
diff --git a/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/AbstractProjectedTest.java b/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/AbstractProjectedTest.java
index 1f40958f6..9ca81db11 100644
--- a/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/AbstractProjectedTest.java
+++ b/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/AbstractProjectedTest.java
@@ -47,28 +47,34 @@ public void setUpSearch() {
     //----------------------------------------------------------------
     @Test
     public void testIncludeExcludeFilter() throws Exception {
-        //an empty column filter is not considered
         search.projection.includes = "id,params,geo_params";
-        handleDisplayedParameter(post(search, ""), Arrays.asList("params.country"));
-        handleDisplayedParameter(get("include", search.projection.includes, ""), Arrays.asList("params.country"));
-        handleHiddenParameter(post(search, ""), Arrays.asList("fullname"));
-        handleHiddenParameter(get("include", search.projection.includes, ""), Arrays.asList("fullname"));
+        handleDisplayedParameter(post(search), Arrays.asList("params.country"));
+        handleDisplayedParameter(get("include", search.projection.includes), Arrays.asList("params.country"));
+        handleHiddenParameter(post(search), Arrays.asList("fullname"));
+        handleHiddenParameter(get("include", search.projection.includes), Arrays.asList("fullname"));
+        handleUnavailableCollection(post(search, ""));
+        handleUnavailableCollection(get("include", search.projection.includes, ""));
+        handleUnavailableCollection(post(search, ""));
+        handleUnavailableCollection(get("include", search.projection.includes, ""));
 
         //unexisting column should not return 404 nor 403 without column filter
         search.projection.includes = "id,params,unexisting";
-        handleDisplayedParameter(post(search, ""), Arrays.asList("params.country"));
-        handleDisplayedParameter(get("include", search.projection.includes, ""), Arrays.asList("params.country"));
-        handleHiddenParameter(post(search, ""), Arrays.asList("fullname"));
-        handleHiddenParameter(get("include", search.projection.includes, ""), Arrays.asList("fullname"));
+        handleDisplayedParameter(post(search), Arrays.asList("params.country"));
+        handleDisplayedParameter(get("include", search.projection.includes), Arrays.asList("params.country"));
+        handleHiddenParameter(post(search), Arrays.asList("fullname"));
+        handleHiddenParameter(get("include", search.projection.includes), Arrays.asList("fullname"));
 
         search.projection.includes = null;
 
-        //an empty column filter is not considered in column filter
         search.projection.excludes = "fullname";
-        handleDisplayedParameter(post(search, ""), Arrays.asList("params.country"));
-        handleDisplayedParameter(get("exclude", search.projection.excludes, ""), Arrays.asList("params.country"));
-        handleHiddenParameter(post(search, ""), Arrays.asList("fullname"));
-        handleHiddenParameter(get("exclude", search.projection.excludes, ""), Arrays.asList("fullname"));
+        handleDisplayedParameter(post(search), Arrays.asList("params.country"));
+        handleDisplayedParameter(get("exclude", search.projection.excludes), Arrays.asList("params.country"));
+        handleHiddenParameter(post(search), Arrays.asList("fullname"));
+        handleHiddenParameter(get("exclude", search.projection.excludes), Arrays.asList("fullname"));
+        handleUnavailableCollection(post(search, ""));
+        handleUnavailableCollection(get("exclude", search.projection.excludes, ""));
+        handleUnavailableCollection(post(search, ""));
+        handleUnavailableCollection(get("exclude", search.projection.excludes, ""));
 
         search.projection.excludes = null;
     }
@@ -197,13 +203,12 @@ public void testReturnedGeometriesFilter() throws Exception {
     }
 
     @Test
-    public void testReturnedGeometriesFilterWithEmptyColumnFilter() throws Exception {
+    public void testReturnedGeometriesFilterWithNoColumnFilter() throws Exception {
 
         search.returned_geometries = "geo_params.geometry,geo_params.second_geometry";
 
-        handleReturnedMultiGeometries(post(search, ""), search.returned_geometries);
+        handleReturnedMultiGeometries(post(search), search.returned_geometries);
         handleReturnedMultiGeometries(givenFilterableRequestParams().param("include", search.projection.includes)
-                .header(COLUMN_FILTER, "")
                 .param("returned_geometries",  search.returned_geometries)
                 .when().get(getUrlPath("geodata"))
                 .then(), search.returned_geometries);
@@ -211,6 +216,22 @@ public void testReturnedGeometriesFilterWithEmptyColumnFilter() throws Exception
         search.returned_geometries = null;
     }
 
+    @Test
+    public void testReturnedGeometriesFilterWithEmptyColumnFilter() throws Exception {
+
+        search.returned_geometries = "geo_params.geometry,geo_params.second_geometry";
+
+        handleReturnedMultiGeometries(post(search), search.returned_geometries);
+        handleUnavailableCollection(post(search, ""));
+        handleUnavailableCollection(givenFilterableRequestParams().param("include", search.projection.includes)
+                .header(COLUMN_FILTER, "")
+                .param("returned_geometries",  search.returned_geometries)
+                .when().get(getUrlPath("geodata"))
+                .then());
+
+        search.returned_geometries = null;
+    }
+
     @Test
     public void testReturnedGeometriesFilterWithAvailableColumns() throws Exception {
 
diff --git a/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/DescribeServiceIT.java b/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/DescribeServiceIT.java
index 1c89ddd83..a581784fb 100644
--- a/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/DescribeServiceIT.java
+++ b/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/DescribeServiceIT.java
@@ -78,11 +78,18 @@ public void testDescribeFeatureWithCollectionBasedColumFiltering() throws Except
     }
 
     protected ValidatableResponse getWithCollectionFilter(Optional<String> collectionFilter) {
-        return given()
-                .header(COLUMN_FILTER, collectionFilter.orElse(""))
-                .when()
-                .get(getUrlPath("geodata"))
-                .then();
+        if (collectionFilter.isPresent()) {
+            return given()
+                    .header(COLUMN_FILTER, collectionFilter.get())
+                    .when()
+                    .get(getUrlPath("geodata"))
+                    .then();
+        } else {
+            return given()
+                    .when()
+                    .get(getUrlPath("geodata"))
+                    .then();
+        }
     }
 
     @Test
diff --git a/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/RawServiceIT.java b/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/RawServiceIT.java
index dd066cf68..6e172d183 100644
--- a/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/RawServiceIT.java
+++ b/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/RawServiceIT.java
@@ -62,10 +62,10 @@ public void testGetArlasHitWithInvalidIdentifier() throws Exception {
     @Test
     public void testGetArlasHitWithEmptyColumnFilter() throws Exception {
         handleRawQuery(
-                givenRawQuery("/ID__170__20DI", Optional.empty(), Optional.of("")));
+                givenRawQuery("/ID__170__20DI", Optional.empty(), Optional.empty()));
 
         handleRawQuery(
-                givenRawQuery("/ID__170__20DI", Optional.of(Boolean.TRUE), Optional.of("")),
+                givenRawQuery("/ID__170__20DI", Optional.of(Boolean.TRUE), Optional.empty()),
                 FLATTEN_CHAR);
     }
 
@@ -108,6 +108,7 @@ public void testGetArlasHitCollectionBasedColumnFiltering() throws Exception {
 
         handleUnavailableCollection(givenRawQuery("/ID__170__20DI", Optional.empty(), Optional.of("notExisting:fullname")));
         handleUnavailableCollection(givenRawQuery("/ID__170__20DI", Optional.of(Boolean.TRUE), Optional.of("notExisting:fullname")));
+        handleUnavailableCollection(givenRawQuery("/ID__170__20DI", Optional.of(Boolean.TRUE), Optional.of("")));
     }
 
     @Override
diff --git a/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/opensearch/OpenSearchServiceIT.java b/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/opensearch/OpenSearchServiceIT.java
index 8026d68f5..afb4d6eac 100644
--- a/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/opensearch/OpenSearchServiceIT.java
+++ b/arlas-tests/src/test/java/io/arlas/server/tests/rest/explore/opensearch/OpenSearchServiceIT.java
@@ -83,7 +83,7 @@ public void testOpenSearchFeature() throws Exception {
 
     @Test
     public void testOpenSearchFeatureWithAvailableColumns() throws Exception {
-        handleOpenSearchFeature(get(""));
+        handleOpenSearchFeature(get());
         handleOpenSearchFeature(get("fullname,params,geo_params.h3"));
     }
 
@@ -102,6 +102,7 @@ public void testOpenSearchFeatureWithCollectionBasedColumnFiltering() throws Exc
         handleOpenSearchFeatureByColumn(get(COLLECTION_NAME + ":params,notExisting:fullname"), withColumns("params."));
         handleOpenSearchFeatureByColumn(get(COLLECTION_NAME + ":params.*,notExisting:full*"), withColumns("params."));
         handleUnavailableCollection(get("notExisting:full*"));
+        handleUnavailableCollection(get(""));
     }
 
     private void handleOpenSearchFeature(ValidatableResponse then) throws Exception {
diff --git a/arlas-tests/src/test/java/io/arlas/server/tests/rest/plugins/eo/TileServiceIT.java b/arlas-tests/src/test/java/io/arlas/server/tests/rest/plugins/eo/TileServiceIT.java
index 5ff4b810b..e881be184 100644
--- a/arlas-tests/src/test/java/io/arlas/server/tests/rest/plugins/eo/TileServiceIT.java
+++ b/arlas-tests/src/test/java/io/arlas/server/tests/rest/plugins/eo/TileServiceIT.java
@@ -183,7 +183,7 @@ public void testTile() throws InterruptedException, IOException {
 
     @Test
     public void testTileWithEmptyColumnFilter() {
-        givenTileQuery("f=id:eq:ID_0_10DI_bottom", Optional.of(""))
+        givenTileQuery("f=id:eq:ID_0_10DI_bottom", Optional.empty())
                 .statusCode(Response.Status.OK.getStatusCode());
     }