diff --git a/arlas-commons/src/main/resources/roles.yaml b/arlas-commons/src/main/resources/roles.yaml index cba001d54..4e57e5caf 100644 --- a/arlas-commons/src/main/resources/roles.yaml +++ b/arlas-commons/src/main/resources/roles.yaml @@ -64,7 +64,7 @@ technicalRoles: - "Building dashboards in ARLAS" permissions: - r:collections:GET - - r:collections/.*:PUT,DELETE + - r:collections/.*:PATCH,PUT,DELETE - r:collections/_export:GET - r:collections/_import:POST - r:persist/resource/.*:PUT,POST,DELETE @@ -94,7 +94,7 @@ technicalRoles: - "M2M account for importing collections" permissions: - r:collections:GET - - r:collections/.*:PUT,DELETE + - r:collections/.*:PATCH,PUT,DELETE - r:collections/_import:POST - r:organisations/.*:GET - r:persist/resource/.*:POST,PUT 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 d78897025..8bec88055 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 @@ -21,9 +21,11 @@ import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serial; import java.io.Serializable; public class CollectionReference implements Serializable { + @Serial private static final long serialVersionUID = 2270763501550669101L; public static final String COLLECTION_NAME = "collection_name"; @@ -31,7 +33,6 @@ public class CollectionReference implements Serializable { public static final String ID_PATH = "id_path"; public static final String GEOMETRY_PATH = "geometry_path"; public static final String CENTROID_PATH = "centroid_path"; - public static final String H3_PATH = "h3_path"; public static final String TIMESTAMP_PATH = "timestamp_path"; public static final String TIMESTAMP_FORMAT = "timestamp_format"; public static final String DEFAULT_TIMESTAMP_FORMAT = "strict_date_optional_time||epoch_millis"; 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 6aff2955a..1ce22231b 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 @@ -52,9 +52,6 @@ public class CollectionReferenceParameters implements Serializable { @JsonProperty(value = CollectionReference.CENTROID_PATH, required = true) public String centroidPath; - @JsonProperty(value = CollectionReference.H3_PATH) - public String h3Path; - @NotEmpty @JsonProperty(value = CollectionReference.TIMESTAMP_PATH, required = true) public String timestampPath; diff --git a/arlas-core/src/main/java/io/arlas/server/core/model/CollectionReferenceUpdate.java b/arlas-core/src/main/java/io/arlas/server/core/model/CollectionReferenceUpdate.java new file mode 100644 index 000000000..2b58aed1f --- /dev/null +++ b/arlas-core/src/main/java/io/arlas/server/core/model/CollectionReferenceUpdate.java @@ -0,0 +1,37 @@ +/* + * 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.server.core.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +import static io.arlas.server.core.model.CollectionReference.ORGANISATIONS_PUBLIC; +import static io.arlas.server.core.model.CollectionReference.ORGANISATIONS_SHARED; + +public class CollectionReferenceUpdate { + @JsonProperty(value = ORGANISATIONS_PUBLIC, required = false) + public Boolean isPublic; + @JsonProperty(value = ORGANISATIONS_SHARED, required = false) + public List sharedWith; + + public CollectionReferenceUpdate() { + } +} 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 81f4710c6..5aa1d9ab5 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,6 +108,23 @@ public CollectionReference putCollectionReference(CollectionReference collection return collectionReference; } + public CollectionReference updateCollectionReference(String collection, + String organisations, + String columnFilter, + boolean isPublic, + List sharedWith) + throws ArlasException { + CollectionReference collectionReference = getCollectionReference(collection, Optional.ofNullable(organisations)); + ColumnFilterUtil.assertCollectionsAllowed(Optional.ofNullable(columnFilter), List.of(collectionReference)); + checkIfAllowedForOrganisations(collectionReference, Optional.ofNullable(organisations), true); + collectionReference.params.collectionOrganisations.isPublic = isPublic; + collectionReference.params.collectionOrganisations.sharedWith = sharedWith; + putCollectionReferenceWithDao(collectionReference); + cacheManager.removeCollectionReference(collectionReference.collectionName); + cacheManager.removeMapping(collectionReference.params.indexName); + return collectionReference; + } + public List describeAllCollections(List collectionReferenceList, Optional columnFilter) throws CollectionUnavailableException { diff --git a/arlas-rest/src/main/java/io/arlas/server/rest/collections/CollectionService.java b/arlas-rest/src/main/java/io/arlas/server/rest/collections/CollectionService.java index 33d361414..f45a5e15b 100644 --- a/arlas-rest/src/main/java/io/arlas/server/rest/collections/CollectionService.java +++ b/arlas-rest/src/main/java/io/arlas/server/rest/collections/CollectionService.java @@ -33,7 +33,6 @@ import io.arlas.server.core.model.*; import io.arlas.server.core.services.CollectionReferenceService; import io.arlas.server.core.utils.CheckParams; -import io.arlas.server.core.utils.CollectionUtil; import io.arlas.server.core.utils.ColumnFilterUtil; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; @@ -45,6 +44,8 @@ import javax.validation.Valid; import javax.validation.constraints.NotNull; import javax.ws.rs.*; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.io.IOException; @@ -158,12 +159,11 @@ public Response importCollections( removeMetacollection(collections); Set allowedCollections = ColumnFilterUtil.getAllowedCollections(Optional.ofNullable(columnFilter)); for (CollectionReference collection : collections) { - collectionReferenceService.checkIfAllowedForOrganisations(collection, Optional.ofNullable(organisations), true); for (String c : allowedCollections) { if ((c.endsWith("*") && collection.collectionName.startsWith(c.substring(0, c.indexOf("*")))) || collection.collectionName.equals(c)) { try { - savedCollections.add(save(collection.collectionName, collection.params, true)); + savedCollections.add(save(collection.collectionName, collection.params, true, organisations)); } catch (Exception e) { throw new ArlasException(e.getMessage()); } @@ -245,10 +245,14 @@ public Response get( @ApiResponse(code = 404, message = "Not Found Error.", response = Error.class), @ApiResponse(code = 500, message = "Arlas Server Error.", response = Error.class)}) public Response put( + @ApiParam(hidden = true) + @HeaderParam(value = ARLAS_ORGANISATION) String organisations, + @ApiParam(name = "collection", value = "collection", required = true) @PathParam(value = "collection") String collection, + @ApiParam(name = "collectionParams", value = "collectionParams", required = true) @@ -269,10 +273,60 @@ public Response put( if (collection != null && collection.equals(META_COLLECTION_NAME)) { throw new NotAllowedException("'" + META_COLLECTION_NAME + "' is not allowed as a name for collections"); } - return ResponseFormatter.getResultResponse(save(collection, collectionReferenceParameters, checkFields == null ? Boolean.TRUE : checkFields)); + return ResponseFormatter.getResultResponse(save(collection, collectionReferenceParameters, + checkFields == null ? Boolean.TRUE : checkFields, organisations)); + } + + @Timed + @Path("{collection}/organisations") + @PATCH + @Produces(UTF8JSON) + @Consumes(UTF8JSON) + @ApiOperation( + value = "Update a collection reference's organisations attribute.", + produces = UTF8JSON, + notes = "Update a collection reference's organisations attribute.", + consumes = UTF8JSON, + response = CollectionReference.class + ) + @ApiResponses(value = {@ApiResponse(code = 200, message = "Successful operation", response = CollectionReference.class), + @ApiResponse(code = 400, message = "JSON parameter malformed.", response = Error.class), + @ApiResponse(code = 404, message = "Not Found Error.", response = Error.class), + @ApiResponse(code = 500, message = "Arlas Server Error.", response = Error.class)}) + public Response patch( + @Context HttpHeaders headers, + @ApiParam(name = "collection", + value = "collection", + required = true) + @PathParam(value = "collection") String collection, + + @ApiParam(name = "collectionParamsUpdate", + value = "collectionParamsUpdate", + required = true) + @NotNull CollectionReferenceUpdate cru, + + @ApiParam(hidden = true) + @HeaderParam(value = COLUMN_FILTER) String columnFilter, + + @ApiParam(hidden = true) + @HeaderParam(value = ARLAS_ORGANISATION) String organisations, + // -------------------------------------------------------- + // ----------------------- FORM ----------------------- + // -------------------------------------------------------- + @ApiParam(name = "pretty", + value = Documentation.FORM_PRETTY, + defaultValue = "false") + @QueryParam(value = "pretty") Boolean pretty + + ) throws ArlasException { + if (collection != null && collection.equals(META_COLLECTION_NAME)) { + throw new NotAllowedException("'" + META_COLLECTION_NAME + "' cannot be updated"); + } + return ResponseFormatter.getResultResponse(collectionReferenceService.updateCollectionReference(collection, organisations, columnFilter, cru.isPublic, cru.sharedWith)); } - public CollectionReference save(String collection, CollectionReferenceParameters collectionReferenceParameters, Boolean checkFields) throws ArlasException { + public CollectionReference save(String collection, CollectionReferenceParameters collectionReferenceParameters, + Boolean checkFields, String organisations) throws ArlasException { CollectionReference collectionReference = new CollectionReference(collection, collectionReferenceParameters); setDefaultInspireParameters(collectionReference); if (inspireConfigurationEnabled) { @@ -280,6 +334,7 @@ public CollectionReference save(String collection, CollectionReferenceParameters CheckParams.checkInvalidDublinCoreElementsForInspire(collectionReference); } CheckParams.checkInvalidInspireParameters(collectionReference); + collectionReferenceService.checkIfAllowedForOrganisations(collectionReference, Optional.ofNullable(organisations), true); return collectionReferenceService.putCollectionReference(collectionReference, checkFields); } @@ -329,26 +384,26 @@ private void setDefaultInspireParameters(CollectionReference collectionReference if (collectionReference.params.inspire == null) { collectionReference.params.inspire = new Inspire(); } - if (collectionReference.params.inspire.keywords == null ||collectionReference.params.inspire.keywords.size() == 0) { + if (collectionReference.params.inspire.keywords == null || collectionReference.params.inspire.keywords.isEmpty()) { collectionReference.params.inspire.keywords = new ArrayList<>(); Keyword k = new Keyword(); k.value = collectionReference.collectionName; collectionReference.params.inspire.keywords.add(k); } - if (collectionReference.params.inspire.inspireUseConditions == null || collectionReference.params.inspire.inspireUseConditions.equals("")) { + if (collectionReference.params.inspire.inspireUseConditions == null || collectionReference.params.inspire.inspireUseConditions.isEmpty()) { collectionReference.params.inspire.inspireUseConditions = "no conditions apply"; } if (collectionReference.params.inspire.inspireURI == null) { collectionReference.params.inspire.inspireURI = new InspireURI(); } - if (collectionReference.params.inspire.inspireURI.code == null || collectionReference.params.inspire.inspireURI.code.equals("")) { + if (collectionReference.params.inspire.inspireURI.code == null || collectionReference.params.inspire.inspireURI.code.isEmpty()) { collectionReference.params.inspire.inspireURI.code = collectionReference.params.dublinCoreElementName.identifier; } - if (collectionReference.params.inspire.inspireURI.namespace == null || collectionReference.params.inspire.inspireURI.namespace.equals("")) { + if (collectionReference.params.inspire.inspireURI.namespace == null || collectionReference.params.inspire.inspireURI.namespace.isEmpty()) { collectionReference.params.inspire.inspireURI.namespace = "ARLAS." + collectionReference.collectionName.toUpperCase(); } //a default language must be specified - if (collectionReference.params.inspire.languages == null || collectionReference.params.inspire.languages.size() == 0) { + if (collectionReference.params.inspire.languages == null || collectionReference.params.inspire.languages.isEmpty()) { collectionReference.params.inspire.languages = new ArrayList<>(); collectionReference.params.inspire.languages.add("eng"); } diff --git a/arlas-tests/src/test/java/io/arlas/server/tests/rest/collections/CollectionServiceIT.java b/arlas-tests/src/test/java/io/arlas/server/tests/rest/collections/CollectionServiceIT.java index 72f7d677b..e7cc0b3cd 100644 --- a/arlas-tests/src/test/java/io/arlas/server/tests/rest/collections/CollectionServiceIT.java +++ b/arlas-tests/src/test/java/io/arlas/server/tests/rest/collections/CollectionServiceIT.java @@ -360,6 +360,53 @@ public void test08WithCollectionFilter() throws Exception { } + @Test + public void test09Organisations() throws Exception { + Map jsonAsMap = getJsonAsMap("bar.com", null, false); + + // PUT new collection + given().contentType("application/json") + .body(jsonAsMap) + .when().put(arlasPath + "collections/bar") + .then().statusCode(200); + + // GET collection + when().get(arlasPath + "collections/bar") + .then().statusCode(200) + .body("collection_name", equalTo("bar")) + .body("params.index_name", equalTo(DataSetTool.DATASET_INDEX_NAME)) + .body("params.id_path", equalTo(DataSetTool.DATASET_ID_PATH)) + .body("params.geometry_path", equalTo(DataSetTool.DATASET_GEOMETRY_PATH)) + .body("params.centroid_path", equalTo(DataSetTool.DATASET_CENTROID_PATH)) + .body("params.timestamp_path", equalTo(DataSetTool.DATASET_TIMESTAMP_PATH)) + .body("params.exclude_fields", equalTo(DataSetTool.DATASET_EXCLUDE_FIELDS)) + .body("params.exclude_wfs_fields", equalTo(DataSetTool.DATASET_EXCLUDE_WFS_FIELDS)) + .body("params.organisations.owner", equalTo("bar.com")) + .body("params.organisations.shared", hasSize(0)) + .body("params.organisations.public", equalTo(Boolean.FALSE)); + + // PATCH collection + given().contentType("application/json") + .body(getOrgAsMap(null, List.of("foo.com"), Boolean.TRUE)) + .when().patch(arlasPath + "collections/bar/organisations") + .then().statusCode(200) + .body("collection_name", equalTo("bar")) + .body("params.index_name", equalTo(DataSetTool.DATASET_INDEX_NAME)) + .body("params.id_path", equalTo(DataSetTool.DATASET_ID_PATH)) + .body("params.geometry_path", equalTo(DataSetTool.DATASET_GEOMETRY_PATH)) + .body("params.centroid_path", equalTo(DataSetTool.DATASET_CENTROID_PATH)) + .body("params.timestamp_path", equalTo(DataSetTool.DATASET_TIMESTAMP_PATH)) + .body("params.exclude_fields", equalTo(DataSetTool.DATASET_EXCLUDE_FIELDS)) + .body("params.exclude_wfs_fields", equalTo(DataSetTool.DATASET_EXCLUDE_WFS_FIELDS)) + .body("params.organisations.owner", equalTo("bar.com")) + .body("params.organisations.shared", hasItems("foo.com")) + .body("params.organisations.public", equalTo(Boolean.TRUE)); + + // DELETE collection + when().delete(arlasPath + "collections/bar") + .then().statusCode(200); + + } private void handleInvalidCollectionParameters(ValidatableResponse then) throws Exception { then.statusCode(400); @@ -380,6 +427,10 @@ private ValidatableResponse put(Map jsonAsMap) { } private Map getJsonAsMap() { + return getJsonAsMap(null, null, null); + } + + private Map getJsonAsMap(String orgOwner, List orgShared, Boolean isPublic) { Map jsonAsMap = new HashMap<>(); jsonAsMap.put(CollectionReference.INDEX_NAME, DataSetTool.DATASET_INDEX_NAME); jsonAsMap.put(CollectionReference.ID_PATH, DataSetTool.DATASET_ID_PATH); @@ -388,9 +439,22 @@ private Map getJsonAsMap() { jsonAsMap.put(CollectionReference.TIMESTAMP_PATH, DataSetTool.DATASET_TIMESTAMP_PATH); jsonAsMap.put(CollectionReference.EXCLUDE_FIELDS, DataSetTool.DATASET_EXCLUDE_FIELDS); jsonAsMap.put(CollectionReference.EXCLUDE_WFS_FIELDS, DataSetTool.DATASET_EXCLUDE_WFS_FIELDS); + if (orgOwner != null) { + jsonAsMap.put(CollectionReference.ORGANISATIONS, getOrgAsMap(orgOwner, orgShared, isPublic)); + } return jsonAsMap; } + private Map getOrgAsMap(String orgOwner, List orgShared, Boolean isPublic) { + Map orgAsMap = new HashMap<>(); + if (orgOwner != null) { + orgAsMap.put(CollectionReference.ORGANISATIONS_OWNER, orgOwner); + } + orgAsMap.put(CollectionReference.ORGANISATIONS_SHARED, Objects.requireNonNullElse(orgShared, Collections.emptyList())); + orgAsMap.put(CollectionReference.ORGANISATIONS_PUBLIC, Objects.requireNonNullElse(isPublic, Boolean.FALSE)); + return orgAsMap; + } + private Object getCollectionDescriptionJsonAsMap() { Map jsonAsMap = new HashMap<>(); jsonAsMap.put(CollectionReference.COLLECTION_DISPLAY_NAME, DataSetTool.DATASET_COLLECTION_DISPLAY_NAME);