/* ====================================================================
*
* Copyright (C) 2007 - 2016 GeoSolutions S.A.S.
* http://www.geo-solutions.it
*
* GPLv3 + Classpath exception
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.
*
* ====================================================================
*
* This software consists of voluntary contributions made by developers
* of GeoSolutions. For more information on GeoSolutions, please see
* <http://www.geo-solutions.it/>.
*
*/
package it.geosolutions.geostore.services.rest.impl;
import it.geosolutions.geostore.core.model.Attribute;
import it.geosolutions.geostore.core.model.Resource;
import it.geosolutions.geostore.core.model.SecurityRule;
import it.geosolutions.geostore.core.model.User;
import it.geosolutions.geostore.core.model.UserGroup;
import it.geosolutions.geostore.core.model.enums.Role;
import it.geosolutions.geostore.services.ResourceService;
import it.geosolutions.geostore.services.SecurityService;
import it.geosolutions.geostore.services.UserGroupService;
import it.geosolutions.geostore.services.UserService;
import it.geosolutions.geostore.services.dto.ShortResource;
import it.geosolutions.geostore.services.dto.search.AndFilter;
import it.geosolutions.geostore.services.dto.search.BaseField;
import it.geosolutions.geostore.services.dto.search.CategoryFilter;
import it.geosolutions.geostore.services.dto.search.FieldFilter;
import it.geosolutions.geostore.services.dto.search.SearchFilter;
import it.geosolutions.geostore.services.dto.search.SearchOperator;
import it.geosolutions.geostore.services.exception.BadRequestServiceEx;
import it.geosolutions.geostore.services.exception.InternalErrorServiceEx;
import it.geosolutions.geostore.services.model.ExtGroupList;
import it.geosolutions.geostore.services.model.ExtResourceList;
import it.geosolutions.geostore.services.model.ExtUserList;
import it.geosolutions.geostore.services.rest.RESTExtJsService;
import it.geosolutions.geostore.services.rest.exception.BadRequestWebEx;
import it.geosolutions.geostore.services.rest.exception.ForbiddenErrorWebEx;
import it.geosolutions.geostore.services.rest.exception.InternalErrorWebEx;
import it.geosolutions.geostore.services.rest.exception.NotFoundWebEx;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import javax.ws.rs.core.SecurityContext;
import net.sf.json.JSON;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.apache.log4j.Logger;
/**
* Class RESTExtJsServiceImpl.
*
* @author Tobia di Pisa (tobia.dipisa at geo-solutions.it)
* @author Emanuele Tajariol (etj at geo-solutions.it)
*/
public class RESTExtJsServiceImpl extends RESTServiceImpl implements RESTExtJsService {
private final static Logger LOGGER = Logger.getLogger(RESTExtJsServiceImpl.class);
private ResourceService resourceService;
private UserService userService;
private UserGroupService groupService;
/**
* @param resourceService
*/
public void setResourceService(ResourceService resourceService) {
this.resourceService = resourceService;
}
/**
* @param userService the userService to set
*/
public void setUserService(UserService userService) {
this.userService = userService;
}
public void setUserGroupService(UserGroupService userGroupService) {
this.groupService = userGroupService;
}
/*
* (non-Javadoc)
*
* @see it.geosolutions.geostore.services.rest.RESTExtJsService#getAllResources (javax.ws.rs.core.UriInfo, javax.ws.rs.core.SecurityContext,
* java.lang.String, java.lang.Integer, java.lang.Integer)
*/
@Override
public String getAllResources(SecurityContext sc, String nameLike,
Integer start, Integer limit)
throws BadRequestWebEx {
if (start == null || limit == null) {
throw new BadRequestWebEx("Request parameters are missing !");
}
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Retrieving paginated resource list (start="+start+" limit="+limit+")");
}
User authUser = null;
try {
authUser = extractAuthUser(sc);
} catch (InternalErrorWebEx ie) {
// serch without user information
LOGGER.warn("Error in validating user (this action should probably be aborted)", ie); // why is this exception caught?
}
int page = start == 0 ? start : start / limit;
try {
nameLike = nameLike.replaceAll("[*]", "%");
// TOOD: implement includeAttributes and includeData
List<ShortResource> resources = resourceService.getList(nameLike, page, limit, authUser);
long count = 0;
if (resources != null && resources.size() > 0) {
count = resourceService.getCountByFilterAndUser(nameLike, authUser);
}
JSONObject result = makeJSONResult(true, count, resources, authUser);
return result.toString();
} catch (BadRequestServiceEx e) {
LOGGER.warn(e.getMessage(), e);
JSONObject obj = makeJSONResult(false, 0, null, authUser);
return obj.toString();
}
}
/*
* (non-Javadoc)
*
* @see it.geosolutions.geostore.services.rest.RESTExtJsService#getResourcesByCategory(javax.ws.rs.core.SecurityContext, java.lang.String,
* java.lang.Integer, java.lang.Integer)
*/
@Override
public String getResourcesByCategory(SecurityContext sc, String categoryName, Integer start,
Integer limit, boolean includeAttributes,
boolean includeData) throws BadRequestWebEx {
return getResourcesByCategory(sc, categoryName, null, start, limit, includeAttributes, includeData);
}
/*
* (non-Javadoc)
*
* @see it.geosolutions.geostore.services.rest.RESTExtJsService#getResourcesByCategory(javax.ws.rs.core.SecurityContext, java.lang.String,
* java.lang.Integer, java.lang.Integer)
*/
@Override
public String getResourcesByCategory(SecurityContext sc, String categoryName, String resourceNameLike, Integer start,
Integer limit, boolean includeAttributes, boolean includeData) throws BadRequestWebEx {
return getResourcesByCategory(sc, categoryName, resourceNameLike, null, start, limit, includeAttributes, includeData);
}
@Override
public String getResourcesByCategory(SecurityContext sc,
String categoryName, String resourceNameLike, String extraAttributes,
Integer start, Integer limit, boolean includeAttributes, boolean includeData)
throws BadRequestWebEx, InternalErrorWebEx
{
if (((start != null) && (limit == null)) || ((start == null) && (limit != null))) {
throw new BadRequestWebEx("start and limit params should be declared together");
}
if (categoryName == null) {
throw new BadRequestWebEx("Category is null");
}
// read extra attributes
List<String> extraAttributesList = extraAttributes != null ?
Arrays.asList(extraAttributes.split(",")):
Collections.EMPTY_LIST;
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("getResourcesByCategory(" + categoryName + ", start=" + start + ", limit="
+ limit + (resourceNameLike != null ? ", search=" + resourceNameLike : ""));
}
User authUser = null;
try {
authUser = extractAuthUser(sc);
} catch (InternalErrorWebEx ie) {
// search without user information
LOGGER.warn("Error in validating user (this action should probably be aborted)", ie); // why is this exception caught?
}
Integer page = null;
if (start != null) {
page = start / limit;
}
try {
SearchFilter filter = new CategoryFilter(categoryName, SearchOperator.EQUAL_TO);
if (resourceNameLike != null) {
resourceNameLike = resourceNameLike.replaceAll("[*]", "%");
filter = new AndFilter(filter, new FieldFilter(BaseField.NAME, resourceNameLike, SearchOperator.ILIKE));
}
List<Resource> resources = resourceService.getResources(filter,
page, limit,
includeAttributes || (extraAttributes != null && !extraAttributes.isEmpty()),
includeData,
authUser);
long count = 0;
if (resources != null && resources.size() > 0) {
count = resourceService.getCountByFilterAndUser(filter, authUser);
}
JSONObject result = makeExtendedJSONResult(true, count, resources,
authUser, extraAttributesList, includeAttributes,
includeData);
return result.toString();
} catch (InternalErrorServiceEx e) {
LOGGER.warn(e.getMessage(), e);
JSONObject obj = makeJSONResult(false, 0, null, authUser);
return obj.toString();
} catch (BadRequestServiceEx e) {
LOGGER.warn(e.getMessage(), e);
JSONObject obj = makeJSONResult(false, 0, null, authUser);
return obj.toString();
}
}
/*
* (non-Javadoc)
*
* @see it.geosolutions.geostore.services.rest.RESTExtJsService#getResourcesList(javax.ws.rs.core.SecurityContext, java.lang.Integer,
* java.lang.Integer, boolean, boolean, it.geosolutions.geostore.services.dto.search.SearchFilter)
*/
@Override
public ExtResourceList getExtResourcesList(SecurityContext sc, Integer start, Integer limit,
boolean includeAttributes, boolean includeData, SearchFilter filter) throws BadRequestWebEx {
if (((start != null) && (limit == null)) || ((start == null) && (limit != null))) {
throw new BadRequestWebEx("start and limit params should be declared together");
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("getResourcesList(start=" + start + ", limit=" + limit
+ ", includeAttributes=" + includeAttributes);
}
User authUser = null;
try {
authUser = extractAuthUser(sc);
} catch (InternalErrorWebEx ie) {
// serch without user information
LOGGER.warn("Error in validating user (this action should probably be aborted)", ie); // why is this exception caught?
}
Integer page = null;
if (start != null) {
page = start / limit;
}
try {
List<Resource> resources = resourceService.getResources(filter, page, limit,
includeAttributes, includeData, authUser);
// Here the Read permission on each resource must be checked due to will be returned the full Resource not just a ShortResource
// N.B. This is a bad method to check the permissions on each requested resource, it can perform 2 database access for each resource.
// Possible optimization -> When retrieving the resources, add to "filter" also another part to load only the allowed resources.
long count = 0;
if (resources != null && resources.size() > 0) {
count = resourceService.getCountByFilterAndUser(filter, authUser);
}
ExtResourceList list = new ExtResourceList(count, resources);
return list;
} catch (InternalErrorServiceEx e) {
LOGGER.warn(e.getMessage(), e);
return null;
} catch (BadRequestServiceEx e) {
LOGGER.warn(e.getMessage(), e);
return null;
}
}
/**
* @param success
* @param count
* @param resources
* @param extraAttributes
* @return JSONObject
*/
private JSONObject makeExtendedJSONResult(boolean success, long count, List<Resource> resources,
User authUser, List<String> extraAttributes, boolean includeAttributes, boolean includeData) {
return makeGeneralizedJSONResult(success, count, resources, authUser, extraAttributes, includeAttributes, includeData);
}
/**
* @param success
* @param count
* @param resources
* @return JSONObject
*/
private JSONObject makeJSONResult(boolean success, long count, List<ShortResource> resources,
User authUser) {
return makeGeneralizedJSONResult(success, count, resources, authUser, null, false, false);
}
@Override
public ExtUserList getUsersList(SecurityContext sc, String nameLike, Integer start,
Integer limit, boolean includeAttributes) throws BadRequestWebEx {
if (((start != null) && (limit == null)) || ((start == null) && (limit != null))) {
throw new BadRequestWebEx("start and limit params should be declared together");
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("getUsersList(start=" + start + ", limit=" + limit);
}
Integer page = null;
if (start != null) {
page = start / limit;
}
try {
nameLike = nameLike.replaceAll("[*]", "%");
List<User> users = userService.getAll(page, limit, nameLike, includeAttributes);
long count = 0;
if (users != null && users.size() > 0) {
count = userService.getCount(nameLike);
}
ExtUserList list = new ExtUserList(count, users);
return list;
} catch (BadRequestServiceEx e) {
LOGGER.warn(e.getMessage(), e);
return null;
}
}
@Override
public ExtGroupList getGroupsList(SecurityContext sc, String nameLike, Integer start,
Integer limit, boolean all) throws BadRequestWebEx {
if (((start != null) && (limit == null)) || ((start == null) && (limit != null))) {
throw new BadRequestWebEx("start and limit params should be declared together");
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("getGroupsList(start=" + start + ", limit=" + limit);
}
Integer page = null;
if (start != null) {
page = start / limit;
}
User authUser = null;
try {
authUser = extractAuthUser(sc);
} catch (InternalErrorWebEx ie) {
// serch without user information
LOGGER.warn("Error in validating user (this action should probably be aborted)", ie); // why is this exception caught?
return null;
}
try {
nameLike = nameLike.replaceAll("[*]", "%");
List<UserGroup> groups = groupService.getAllAllowed(authUser, page, limit, nameLike, all);
long count = 0;
if (groups != null && groups.size() > 0) {
count = groupService.getCount(authUser, nameLike, all);
}
ExtGroupList list = new ExtGroupList(count, groups);
return list;
} catch (BadRequestServiceEx e) {
LOGGER.warn(e.getMessage(), e);
return null;
}
}
/* (non-Javadoc)
* @see it.geosolutions.geostore.services.rest.impl.RESTServiceImpl#getSecurityService()
*/
@Override
protected SecurityService getSecurityService() {
return resourceService;
}
/**
* Generalize method. Use this.ResourceEnvelop class
*
* @param success
* @param count
* @param resources
* @param authUser
* @param extraAttributes
* @return
*/
private JSONObject makeGeneralizedJSONResult(boolean success, long count, List<?> resources,
User authUser, List<String> extraAttributes, boolean includeAttributes, boolean includeData) {
JSONObject jsonObj = new JSONObject();
jsonObj.put("success", success);
jsonObj.put("totalCount", count);
if (resources != null) {
Iterator<?> iterator = resources.iterator();
JSON result;
int size = resources.size();
if (size == 0) {
result = null;
} else if (size > 1) {
result = new JSONArray();
} else {
result = new JSONObject();
}
while (iterator.hasNext()) {
Object obj = iterator.next();
ResourceEnvelop sr = null;
if (obj instanceof Resource) {
sr = new ResourceEnvelop((Resource) obj, authUser);
} else if (obj instanceof ShortResource) {
sr = new ResourceEnvelop((ShortResource) obj, authUser);
}
if (sr != null) {
JSONObject jobj = new JSONObject();
jobj.element("canDelete", sr.isCanDelete());
jobj.element("canEdit", sr.isCanEdit());
jobj.element("canCopy", sr.isCanCopy());
Date date = sr.getCreation();
if (date != null) {
jobj.element("creation", date.toString());
}
date = sr.getLastUpdate();
if (date != null) {
jobj.element("lastUpdate", date.toString());
}
String description = sr.getDescription();
if (description != null) {
jobj.element("description", description);
}
jobj.element("id", sr.getId());
jobj.element("name", sr.getName());
String owner = sr.getOwner();
// Append extra attributes
if (sr.getAttribute() != null) {
for (Attribute at : sr.getAttribute()) {
if (includeAttributes || (extraAttributes != null && extraAttributes.contains(at.getName()))) {
jobj.element(at.getName(), at.getValue());
}
if ("owner".equals(at.getName())) {
owner = at.getValue();
}
}
}
if (includeData) {
jobj.element("data", ((Resource) obj).getData().getData());
}
//get owner
if (owner != null) {
jobj.element("owner", owner);
}
if (result instanceof JSONArray) {
((JSONArray) result).add(jobj);
} else {
result = jobj;
}
}
}
jsonObj.put("results", result != null ? result.toString() : "");
} else {
jsonObj.put("results", "");
}
return jsonObj;
}
@Override
public ShortResource getResource(SecurityContext sc, long id) throws NotFoundWebEx {
User authUser = extractAuthUser(sc);
ResourceAuth auth = getResourceAuth(authUser, id);
if(! auth.canRead ){
throw new ForbiddenErrorWebEx("Resource is protected");
}
Resource ret = resourceService.get(id);
if (ret == null) {
throw new NotFoundWebEx("Resource not found");
}
ShortResource sr = new ShortResource(ret);
sr.setCanEdit(auth.canWrite);
sr.setCanDelete(auth.canWrite);
return sr;
}
/**
* Encapsulates resource/short resource and credentials to perform operations with resources
*
* @author adiaz
*
*/
private class ResourceEnvelop {
ShortResource sr;
Resource r;
String owner;
User authUser;
boolean canEdit = false;
boolean canDelete = false;
/**
* Create a resource envelop based on a short resource
*
* @param sr Short resource
* @param authUser user logged
*/
private ResourceEnvelop(ShortResource sr, User authUser) {
super();
this.sr = sr;
this.authUser = authUser;
readSecurity();
}
/**
* Create a resource envelop based on a resource
*
* @param r resource
* @param authUser user logged
*/
private ResourceEnvelop(Resource r, User authUser) {
super();
this.r = r;
this.authUser = authUser;
readSecurity();
}
/**
* Read security for edit and delete
*/
private void readSecurity() {
if (sr != null) {
canDelete = sr.isCanDelete();
canEdit = sr.isCanEdit();
} else
// ///////////////////////////////////////////////////////////////////////
// This fragment checks if the authenticated user can modify and
// delete
// the loaded resource (and associated attributes and stored
// data).
// This to inform the client in HTTP response result.
// ///////////////////////////////////////////////////////////////////////
if (authUser != null) {
if (authUser.getRole().equals(Role.ADMIN)) {
canEdit = true;
canDelete = true;
} else {
//get security rules for groups
List<String> groups = new ArrayList<String>();
for (UserGroup g : authUser.getGroups()) {
groups.add(g.getGroupName());
}
for (SecurityRule rule : resourceService
.getGroupSecurityRule(groups,
r.getId())) {
//GUEST users can not access to the delete and edit(resource,data blob is editable) services
//so only authenticated users with
if (rule.isCanWrite() && !authUser.getRole().equals(Role.GUEST)) {
canEdit = true;
canDelete = true;
break;
}
}
//get security rules for user
for (SecurityRule rule : resourceService
.getUserSecurityRule(authUser.getName(),
r.getId())) {
User owner = rule.getUser();
UserGroup userGroup = rule.getGroup();
if (owner != null) {
if (owner.getId().equals(authUser.getId())) {
if (rule.isCanWrite()) {
canEdit = true;
canDelete = true;
break;
}
}
} else if (userGroup != null) {
if (authUser.getGroups() != null
&& authUser.getGroups().contains( // FIXME: Given object cannot contain instances of String (expected UserGroup)
userGroup.getGroupName())) {
if (rule.isCanWrite()) {
canEdit = true;
canDelete = true;
break;
}
}
}
}
}
}
}
/**
* @return true if the logged user is owner of the resource and false otherwise
*/
boolean isCanDelete() {
return canDelete;
}
/**
* @return true if the logged user is owner of the resource and false otherwise
*/
boolean isCanEdit() {
return canEdit;
}
/**
* @return data creation
*/
Date getCreation() {
return sr != null ? sr.getCreation() : r.getCreation();
}
/**
* @return last update
*/
Date getLastUpdate() {
return sr != null ? sr.getLastUpdate() : r.getLastUpdate();
}
/**
* @return resource description
*/
String getDescription() {
return sr != null ? sr.getDescription() : r.getDescription();
}
/**
* @return resource id
*/
long getId() {
return sr != null ? sr.getId() : r.getId();
}
/**
* @return resource name
*/
String getName() {
return sr != null ? sr.getName() : r.getName();
}
/**
* @return resource attributes if contains
*/
List<Attribute> getAttribute() {
return r != null ? r.getAttribute() : null;
}
/**
* @return true if there are an user logged
*/
public Boolean isCanCopy() {
return authUser != null;
}
/**
* @return resource owner
*/
public String getOwner() {
return owner;
}
}
}