gcat/src/main/java/org/gcube/gcat/rest/administration/Configuration.java

579 lines
23 KiB
Java

package org.gcube.gcat.rest.administration;
import java.util.Iterator;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.ForbiddenException;
import javax.ws.rs.GET;
import javax.ws.rs.InternalServerErrorException;
//import javax.ws.rs.NotAuthorizedException;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.Response.Status;
import javax.xml.ws.WebServiceException;
import org.gcube.com.fasterxml.jackson.databind.ObjectMapper;
import org.gcube.com.fasterxml.jackson.databind.node.ObjectNode;
//import org.gcube.common.authorization.control.annotations.AuthorizationControl;
import org.gcube.common.authorization.utils.manager.SecretManagerProvider;
import org.gcube.gcat.annotation.PATCH;
import org.gcube.gcat.annotation.PURGE;
import org.gcube.gcat.api.GCatConstants;
import org.gcube.gcat.api.roles.Role;
import org.gcube.gcat.configuration.CatalogueConfigurationFactory;
import org.gcube.gcat.configuration.ServiceCatalogueConfiguration;
import org.gcube.gcat.persistence.ckan.CKANUser;
import org.gcube.gcat.persistence.ckan.CKANUserCache;
import org.gcube.gcat.rest.BaseREST;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.webcohesion.enunciate.metadata.rs.ResourceGroup;
import com.webcohesion.enunciate.metadata.rs.ResourceLabel;
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.swagger.OperationId;
/**
* The catalogue configuration for the context of the request
* (i.e. the context where the token has been generated).
*
* Only Catalogue-Managers are able to invoke non-safe methods.
*
* @author Luca Frosini (ISTI - CNR)
*/
@Path(Configuration.CONFIGURATIONS)
@ResourceGroup("Administration APIs")
@ResourceLabel("Configuration APIs")
public class Configuration extends BaseREST implements org.gcube.gcat.api.interfaces.Configuration<Response,Response> {
private static Logger logger = LoggerFactory.getLogger(Configuration.class);
public static final String CONTEXT_FULLNAME_PARAMETER = "CONTEXT_FULLNAME_PARAMETER";
protected String checkContext(String context) throws WebServiceException {
if(context==null || context.compareTo("")==0) {
throw new BadRequestException("Please provide a valid context as path parameter");
}
String c = SecretManagerProvider.instance.get().getContext();
if(context.compareTo(Configuration.CURRENT_CONTEXT_PATH_PARAMETER)==0) {
return c;
}
if(context.compareTo(c)!=0) {
throw new BadRequestException("Context provided as path parameter (i.e. " + context + ") does not match with token request context (i.e. " + c + ")");
}
return c;
}
protected String checkContext(String context, ServiceCatalogueConfiguration catalogueConfiguration) {
String c = checkContext(context);
if(c.compareTo(catalogueConfiguration.getContext())!=0) {
throw new BadRequestException("Context provided in the configuration (i.e. " + catalogueConfiguration.getContext() + ") does not match with token request context (i.e. " + c + ")");
}
return c;
}
protected void checkRole(Role required) {
CKANUser ckanUser = CKANUserCache.getCurrrentCKANUser();
if(ckanUser.getRole().ordinal() < required.ordinal()) {
throw new ForbiddenException("To perform such a request you must have " + required.getPortalRole() + " role");
}
}
private String createOrUpdate(ServiceCatalogueConfiguration catalogueConfiguration) throws WebServiceException {
try {
ServiceCatalogueConfiguration gotCatalogueConfiguration = CatalogueConfigurationFactory.createOrUpdate(catalogueConfiguration);
String configuration = gotCatalogueConfiguration.toJsonString();
logger.debug("The new configuration in context {} is {}", catalogueConfiguration.getContext(), configuration);
return configuration;
}catch (WebApplicationException e) {
throw e;
}catch (Exception e) {
throw new InternalServerErrorException(e);
}
}
/**
* This API allows to create the catalogue configuration for the
* context of the request (i.e. the context where the token has been generated)
* using the json provided as request body.<br/>
*
* Only a <a href="../docs/index.html#roles">Catalogue-Manager</a> can invoke this API.<br/>
*
* The configuration will be persisted in the infrastructure
* Information System (IS) in the context of the request.<br/>
* * A Configuration is described by the following attributes:
*
* <dl>
*
* <dt>context (string)</dt>
* <dd>it must contains the same value of requesting context;</dd>
*
* <dt style="margin-top: 5px;">defaultOrganization (string)</dt>
* <dd>
* the default ckan organization where an item is created for
* the context of the request;
* </dd>
*
* <dt style="margin-top: 5px;">supportedOrganizations (array of string)</dt>
* <dd>
* it defines the list of organization where an item can be created.
* All the organizations must be present in the catalogue of the VRE.
* It must contains at least the defaultOrganization value;
* </dd>
*
* <dt style="margin-top: 5px;">sysAdminToken (string)</dt>
* <dd>
* the token can be provided encrypted with the context key as well as plain.
* It any case it is persisted encrypted in IS;
* </dd>
*
* <dt style="margin-top: 5px;">ckanURL (string)</dt>
* <dd>
* the Ckan URL used for the catalogue of the VRE.
* </dd>
*
* <dt style="margin-top: 5px;">solrURL (string)</dt>
* <dd>
* the Solr URL used by the Ckan instance.
* This is normally, but not necessary, the same URL of Ckan plus the path <code>/solr</code>
* </dd>
*
* <dt style="margin-top: 5px;">socialPostEnabled (bool)</dt>
* <dd>
* it indicates if the social posts are enabled in the context of the request.
* The creation of a social post can be requested by a user when he/she creates
* an item;
* </dd>
*
* <dt style="margin-top: 5px;">notificationToUsersEnabled (bool)</dt>
* <dd>
* it indicates if a notification must be created when a social post
* is created. It is ignored if <code>socialPostEnabled=false</code>;
* </dd>
*
* <dt style="margin-top: 5px;">moderationEnabled (bool)</dt>
* <dd>
* it indicates if the catalogue is moderated.
* See <a href="../docs/index.html#moderated-catalogues">Moderated Catalogues</a>
* </dd>
*
* <dt style="margin-top: 5px;">ckanDB (object)</dt>
* <dd>
* it contains the url, username and password to contact the db used by the
* Ckan instance used for the catalogue of the VRE.
* The password can be provided encrypted with the context key as well as plain.
* It any case it is persisted encrypted in IS.
* </dd>
*
* </dl>
*
* Here is an example
*
* <pre>
* {
* "context": "/gcube/devsec/devVRE",
* "defaultOrganization": "devvre",
* "supportedOrganizations": [ "devvre" "nextnext" ],
* "sysAdminToken": "SYS_ADMIN_TOKEN_HERE",
* "ckanURL": "https://ckan-d-d4s.d4science.org",
* "solrURL": "https://ckan-d-d4s.d4science.org/solr/",
* "socialPostEnabled": false,
* "notificationToUsersEnabled": true,
* "moderationEnabled": true,
* "ckanDB": {
* "url": "jdbc:postgresql://ckan-d-d4s.d4science.org:5432/ckan_dev",
* "username": "ckan",
* "password": "PWD_HERE"
* }
* }
* </pre>
*
*
* @param json the configuration representation
* @return the created configuration
* @throws WebServiceException when the request fails
*
* @requestExample application/json;charset=UTF-8 classpath:/api-docs-examples/configuration/create-configuration-request.json
* @responseExample application/json;charset=UTF-8 classpath:/api-docs-examples/configuration/create-configuration-response.json
*/
@POST
@Consumes(GCatConstants.APPLICATION_JSON_CHARSET_UTF_8)
@Produces(GCatConstants.APPLICATION_JSON_CHARSET_UTF_8)
@StatusCodes ({
@ResponseCode(code = 201, condition = "Catalogue configuration successfully created."),
@ResponseCode(code = 401, condition = "Only Catalogue-Managers can create catalogue configuration."),
@ResponseCode(code = 500, condition = "Error while persisting catalogue configuration."),
})
@Override
// @AuthorizationControl(allowedRoles={Role.CATALOGUE_MANAGER}, exception=NotAuthorizedException.class)
public Response create(String json) throws WebServiceException {
try {
ServiceCatalogueConfiguration catalogueConfiguration = ServiceCatalogueConfiguration.getServiceCatalogueConfiguration(json);
checkContext(CURRENT_CONTEXT_PATH_PARAMETER, catalogueConfiguration);
String ret = createOrUpdate(catalogueConfiguration);
ResponseBuilder responseBuilder = Response.status(Status.CREATED);
if(ret!=null) {
responseBuilder.entity(ret).type(GCatConstants.APPLICATION_JSON_CHARSET_UTF_8);
}
return responseBuilder.build();
}catch (WebApplicationException e) {
throw e;
}catch (Exception e) {
throw new InternalServerErrorException(e);
}
}
/**
* This API allows to read the catalogue configuration for the
* current context (i.e. the context where the token has been generated).<br/>
*
* Please refers to <a href="#resource_Configuration_create_json_POST">Create Configuration API</a>
* for the list of configuration properties and their meaning.<br/>
*
* This API can be invoked by any user with at least
* <a href="../docs/index.html#roles">Catalogue-Editor</a> role.<br/>
*
* For any non <a href="../docs/index.html#roles">Catalogue-Manager</a> user the configuration
* will contain only a subset of the properties of the configuration.
* Here is an example:
*
* <pre>
* {
* "context": "/gcube/devsec/devVRE",
* "defaultOrganization": "devvre",
* "supportedOrganizations": [ "devvre" "nextnext" ],
* "ckanURL": "https://ckan-d-d4s.d4science.org",
* "solrURL": "https://ckan-d-d4s.d4science.org/solr/",
* "socialPostEnabled": false,
* "notificationToUsersEnabled": true,
* "moderationEnabled": true
* }
* </pre>
*
* @param context must contains the context of the request
* (i.e. the context where the token has been generated)
* or the placeholder <code>CURRENT_CONTEXT</code>.<br/>
* Please note that the context must be URL encoded,
* e.g. /gcube/devsec/devVRE -> %2Fgcube%2Fdevsec%2FdevVRE
*
* @pathExample /configurations/CURRENT_CONTEXT
* @responseExample application/json;charset=UTF-8 classpath:/api-docs-examples/configuration/read-configuration-response.json
*/
@GET
@Path("/{" + CONTEXT_FULLNAME_PARAMETER + "}")
@Produces(GCatConstants.APPLICATION_JSON_CHARSET_UTF_8)
// @AuthorizationControl(allowedRoles={Role.CATALOGUE_EDITOR, Role.CATALOGUE_ADMIN, Role.CATALOGUE_MANAGER}, exception=NotAuthorizedException.class)
@StatusCodes ({
@ResponseCode(code = 200, condition = "Catalogue configuration successfully read."),
@ResponseCode(code = 401, condition = "Only User with role Catalogue-Editors or above can read a catalogue configuration."),
@ResponseCode(code = 500, condition = "Error while reading catalogue configuration."),
})
/**
* @param context
* @return
* @throws WebServiceException
*/
public Response read(@PathParam(CONTEXT_FULLNAME_PARAMETER) String context) throws WebServiceException {
try {
checkContext(context);
return read();
}catch (WebApplicationException e) {
throw e;
}catch (Exception e) {
throw new InternalServerErrorException(e);
}
}
@Override
public Response read() throws WebServiceException {
try {
ServiceCatalogueConfiguration catalogueConfiguration = CatalogueConfigurationFactory.getInstance();
String configuration = catalogueConfiguration.toJsonString();
logger.debug("Configuration in context {} is {}", catalogueConfiguration.getContext(), configuration);
ResponseBuilder responseBuilder = Response.status(Status.OK);
if(configuration!=null) {
responseBuilder.entity(configuration).type(GCatConstants.APPLICATION_JSON_CHARSET_UTF_8);
}
return responseBuilder.build();
}catch (WebApplicationException e) {
throw e;
}catch (Exception e) {
throw new InternalServerErrorException(e);
}
}
/**
* This API allows to create/update the catalogue configuration for the
* context of the request (i.e. the context where the token has been generated)
* using the json provided as request body.<br/>
*
* Only a <a href="../docs/index.html#roles">Catalogue-Manager</a> can invoke this API.<br/>
*
* The configuration will be persisted in the infrastructure
* Information System (IS) in the context of the request.<br/>
*
* @param context must contains the context of the request
* (i.e. the context where the token has been generated)
* or the placeholder <code>CURRENT_CONTEXT</code>.<br/>
* Please note that the context must be URL encoded,
* e.g. /gcube/devsec/devVRE -> %2Fgcube%2Fdevsec%2FdevVRE
* @param json the configuration representation
* @return the updated/created configuration
* @throws WebServiceException when the request fails
*
* @pathExample /configurations/CURRENT_CONTEXT
* @requestExample application/json;charset=UTF-8 classpath:/api-docs-examples/configuration/create-configuration-request.json
* @responseExample application/json;charset=UTF-8 classpath:/api-docs-examples/configuration/create-configuration-response.json
*/
@PUT
@Path("/{" + CONTEXT_FULLNAME_PARAMETER + "}")
@Consumes(GCatConstants.APPLICATION_JSON_CHARSET_UTF_8)
@Produces(GCatConstants.APPLICATION_JSON_CHARSET_UTF_8)
// @AuthorizationControl(allowedRoles={Role.CATALOGUE_MANAGER}, exception=NotAuthorizedException.class)
@StatusCodes ({
@ResponseCode(code = 200, condition = "Catalogue configuration successfully created/updated."),
@ResponseCode(code = 401, condition = "Only Catalogue-Managers can create/update catalogue configuration."),
@ResponseCode(code = 500, condition = "Error while creating/updating catalogue configuration."),
})
@OperationId("Create or Update")
public String createOrUpdate(@PathParam(CONTEXT_FULLNAME_PARAMETER) String context, String json) throws WebServiceException {
try {
ServiceCatalogueConfiguration catalogueConfiguration = ServiceCatalogueConfiguration.getServiceCatalogueConfiguration(json);
checkContext(context, catalogueConfiguration);
return createOrUpdate(catalogueConfiguration);
}catch (WebApplicationException e) {
throw e;
}catch (Exception e) {
throw new InternalServerErrorException(e);
}
}
@Override
public Response update(String json) throws WebServiceException {
try {
ServiceCatalogueConfiguration catalogueConfiguration = ServiceCatalogueConfiguration.getServiceCatalogueConfiguration(json);
checkContext(CURRENT_CONTEXT_PATH_PARAMETER);
catalogueConfiguration = CatalogueConfigurationFactory.createOrUpdate(catalogueConfiguration);
String configuration = catalogueConfiguration.toJsonString();
logger.debug("Configuration in context {} has been updated to {}", catalogueConfiguration.getContext(), configuration);
ResponseBuilder responseBuilder = Response.status(Status.OK);
if(configuration!=null) {
responseBuilder.entity(configuration).type(GCatConstants.APPLICATION_JSON_CHARSET_UTF_8);
}
return responseBuilder.build();
}catch (WebApplicationException e) {
throw e;
}catch (Exception e) {
throw new InternalServerErrorException(e);
}
}
/**
* This API allows to patch the catalogue configuration for the
* context of the request (i.e. the context where the token has been generated)
* using the json provided as request body.<br/>
* Only a <a href="../docs/index.html#roles">Catalogue-Manager</a> can invoke this API. <br/>
* The configuration will be persisted in the infrastructure
* Information System (IS) in the context of the request.<br/>
*
* Please refers to <a href="#resource_Configuration_create_json_POST">Create Configuration API</a>
* for the list of configuration properties and their meaning.
*
* @param context must contains the context of the request
* (i.e. the context where the token has been generated)
* or the placeholder <code>CURRENT_CONTEXT</code>.<br/>
* Please note that the context must be URL encoded,
* e.g. /gcube/devsec/devVRE -> %2Fgcube%2Fdevsec%2FdevVRE
* @param json must contains only the properties of the configuratoin the user wants to change.
* @return the patched configuration.
* @throws WebServiceException when the request fails
*
* @pathExample /configurations/CURRENT_CONTEXT
* @requestExample application/json;charset=UTF-8 classpath:/api-docs-examples/configuration/create-configuration-request.json
* @responseExample application/json;charset=UTF-8 classpath:/api-docs-examples/configuration/create-configuration-response.json
*/
@PATCH
@Path("/{" + CONTEXT_FULLNAME_PARAMETER + "}")
@Consumes(GCatConstants.APPLICATION_JSON_CHARSET_UTF_8)
@Produces(GCatConstants.APPLICATION_JSON_CHARSET_UTF_8)
// @AuthorizationControl(allowedRoles={Role.CATALOGUE_MANAGER}, exception=NotAuthorizedException.class)
@StatusCodes ({
@ResponseCode(code = 200, condition = "Catalogue configuration successfully updated."),
@ResponseCode(code = 401, condition = "Only Catalogue-Managers can update catalogue configuration."),
@ResponseCode(code = 500, condition = "Error while updating catalogue configuration."),
})
public Response patch(@PathParam(CONTEXT_FULLNAME_PARAMETER) String context, String json) throws WebServiceException {
try {
checkContext(context);
return patch(json);
}catch (WebApplicationException e) {
throw e;
}catch (Exception e) {
throw new InternalServerErrorException(e);
}
}
@Override
public Response patch(String json) throws WebServiceException {
try {
ServiceCatalogueConfiguration catalogueConfiguration = CatalogueConfigurationFactory.getInstance();
ObjectMapper mapper = new ObjectMapper();
ObjectNode node = (ObjectNode) mapper.readTree(json);
if(node.has(ServiceCatalogueConfiguration.CONTEXT_KEY)) {
String context = node.get(ServiceCatalogueConfiguration.CONTEXT_KEY).asText();
String c = SecretManagerProvider.instance.get().getContext();
if(c.compareTo(context)!=0) {
throw new BadRequestException("Context provided in the configuration (i.e. " + catalogueConfiguration.getContext() + ") does not match with token request context (i.e. " + c + ")");
}
node.remove(CURRENT_CONTEXT_PATH_PARAMETER);
}
ObjectNode configuration = mapper.valueToTree(catalogueConfiguration);
Iterator<String> fieldNames = node.fieldNames();
while(fieldNames.hasNext()) {
String fieldName = fieldNames.next();
configuration.set(fieldName, node.get(fieldName));
}
ServiceCatalogueConfiguration newCatalogueConfiguration = ServiceCatalogueConfiguration.getServiceCatalogueConfiguration(configuration);
newCatalogueConfiguration = CatalogueConfigurationFactory.createOrUpdate(newCatalogueConfiguration);
String ret = newCatalogueConfiguration.toJsonString();
logger.debug("Configuration in context {} has been patched to {}", catalogueConfiguration.getContext(), ret);
ResponseBuilder responseBuilder = Response.status(Status.OK);
if(ret!=null) {
responseBuilder.entity(ret).type(GCatConstants.APPLICATION_JSON_CHARSET_UTF_8);
}
return responseBuilder.build();
}catch (WebApplicationException e) {
throw e;
}catch (Exception e) {
throw new InternalServerErrorException(e);
}
}
/**
* It removes from the cache the configuration for the
* context of the request (i.e. the context where the token has been generated).<br/>
*
* This API forces the service to read again from the Information System (IS)
* the catalogue configuration for the context of the request.<br/>
*
* If the user specifies the <code>purge</code> query parameter this API
* remove the configuration from the IS. Please note that this implies that
* the catalogue is no more configured for the context of the request.
*
*
* @param context context must contains the context of the request
* (i.e. the context where the token has been generated)
* or the placeholder <code>CURRENT_CONTEXT</code>.<br/>
* Please note that the context must be URL encoded,
* e.g. /gcube/devsec/devVRE -> %2Fgcube%2Fdevsec%2FdevVRE
* @param purge indicates to the service to remvoe the configuration from the IS
* @throws WebServiceException
*
* @pathExample /configurations/CURRENT_CONTEXT
*/
@DELETE
@Path("/{" + CONTEXT_FULLNAME_PARAMETER + "}")
// @AuthorizationControl(allowedRoles={Role.CATALOGUE_MANAGER}, exception=NotAuthorizedException.class)
@StatusCodes ({
@ResponseCode(code = 200, condition = "Catalogue configuration successfully deleted."),
@ResponseCode(code = 401, condition = "Only Catalogue-Managers can delete catalogue configuration."),
@ResponseCode(code = 500, condition = "Error while deleting catalogue configuration."),
})
public Response delete(@PathParam(CONTEXT_FULLNAME_PARAMETER) String context,
@QueryParam(GCatConstants.PURGE_QUERY_PARAMETER) @DefaultValue("false") Boolean purge) throws WebServiceException {
try {
checkContext(context);
if(purge) {
return purge();
}else {
return delete();
}
}catch (WebApplicationException e) {
throw e;
}catch (Exception e) {
throw new InternalServerErrorException(e);
}
}
// Remove the configuration from cache and force reload
@Override
public Response delete() throws WebServiceException {
try {
CatalogueConfigurationFactory.renew();
return Response.status(Status.NO_CONTENT).build();
}catch (WebApplicationException e) {
throw e;
}catch (Exception e) {
throw new InternalServerErrorException(e);
}
}
/**
* It removes remove the configuration from the IS for the
* context of the request (i.e. the context where the token has been generated).<br/>
*
* Please note that this implies that
* the catalogue is no more configured for the context of the request.
*
* @param context context must contains the context of the request
* (i.e. the context where the token has been generated)
* or the placeholder <code>CURRENT_CONTEXT</code>.<br/>
* Please note that the context must be URL encoded,
* e.g. /gcube/devsec/devVRE -> %2Fgcube%2Fdevsec%2FdevVRE
* @throws WebServiceException
*
* @pathExample /configurations/CURRENT_CONTEXT
*
*/
@PURGE
@Path("/{" + CONTEXT_FULLNAME_PARAMETER + "}")
// @AuthorizationControl(allowedRoles={Role.CATALOGUE_MANAGER}, exception=NotAuthorizedException.class)
@StatusCodes ({
@ResponseCode(code = 200, condition = "Catalogue configuration successfully deleted."),
@ResponseCode(code = 401, condition = "Only Catalogue-Managers can delete catalogue configuration."),
@ResponseCode(code = 500, condition = "Error while deleting catalogue configuration."),
})
public Response purge(@PathParam(CONTEXT_FULLNAME_PARAMETER) String context) throws WebServiceException {
try {
checkContext(context);
return purge();
}catch (WebApplicationException e) {
throw e;
}catch (Exception e) {
throw new InternalServerErrorException(e);
}
}
// Remove the configuration from cache and from IS
@Override
public Response purge() throws WebServiceException {
try {
CatalogueConfigurationFactory.purge();
return Response.status(Status.NO_CONTENT).build();
}catch (WebApplicationException e) {
throw e;
}catch (Exception e) {
throw new InternalServerErrorException(e);
}
}
}