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 { 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.
* * Only a Catalogue-Manager can invoke this API.
* * The configuration will be persisted in the infrastructure * Information System (IS) in the context of the request.
* * A Configuration is described by the following attributes: * *
* *
context (string)
*
it must contains the same value of requesting context;
* *
defaultOrganization (string)
*
* the default ckan organization where an item is created for * the context of the request; *
* *
supportedOrganizations (array of string)
*
* 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; *
* *
sysAdminToken (string)
*
* the token can be provided encrypted with the context key as well as plain. * It any case it is persisted encrypted in IS; *
* *
ckanURL (string)
*
* the Ckan URL used for the catalogue of the VRE. *
* *
solrURL (string)
*
* the Solr URL used by the Ckan instance. * This is normally, but not necessary, the same URL of Ckan plus the path /solr *
* *
socialPostEnabled (bool)
*
* 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; *
* *
notificationToUsersEnabled (bool)
*
* it indicates if a notification must be created when a social post * is created. It is ignored if socialPostEnabled=false; *
* *
moderationEnabled (bool)
*
* it indicates if the catalogue is moderated. * See Moderated Catalogues *
* *
ckanDB (object)
*
* 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. *
* *
* * Here is an example * *
	 * {
	 *	"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"
	 *	}
	 * }
	 * 
* * * @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).
* * Please refers to Create Configuration API * for the list of configuration properties and their meaning.
* * This API can be invoked by any user with at least * Catalogue-Editor role.
* * For any non Catalogue-Manager user the configuration * will contain only a subset of the properties of the configuration. * Here is an example: * *
	 * 	{
	 *		"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
	 * 	}
	 * 
* * @param context must contains the context of the request * (i.e. the context where the token has been generated) * or the placeholder CURRENT_CONTEXT.
* 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.
* * Only a Catalogue-Manager can invoke this API.
* * The configuration will be persisted in the infrastructure * Information System (IS) in the context of the request.
* * @param context must contains the context of the request * (i.e. the context where the token has been generated) * or the placeholder CURRENT_CONTEXT.
* 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.
* Only a Catalogue-Manager can invoke this API.
* The configuration will be persisted in the infrastructure * Information System (IS) in the context of the request.
* * Please refers to Create Configuration API * 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 CURRENT_CONTEXT.
* 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 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).
* * This API forces the service to read again from the Information System (IS) * the catalogue configuration for the context of the request.
* * If the user specifies the purge 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 CURRENT_CONTEXT.
* 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).
* * 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 CURRENT_CONTEXT.
* 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); } } }