VIVO-618 Revise SearchService to parse uploaded files correctly and to respond properly to authentication errors.

This commit is contained in:
j2blake 2013-12-05 16:36:26 -05:00
parent 0944a0d883
commit d08c3f61ff
10 changed files with 557 additions and 618 deletions

View file

@ -28,7 +28,6 @@ import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.propstmt.DropDataPr
import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.propstmt.DropObjectPropertyStatement;
import edu.cornell.mannlib.vitro.webapp.beans.UserAccount;
import edu.cornell.mannlib.vitro.webapp.controller.authenticate.Authenticator;
import edu.cornell.mannlib.vitro.webapp.controller.authenticate.BasicAuthenticator;
/**
* A collection of static methods to help determine whether requested actions
@ -56,7 +55,7 @@ public class PolicyHelper {
IdentifierBundle ids = RequestIdentifiers.getIdBundleForRequest(req);
return isAuthorizedForActions(ids, policy, actions);
}
/**
* Are these actions authorized for these identifiers by these policies?
*/
@ -66,47 +65,53 @@ public class PolicyHelper {
}
/**
* Is the email/password authorized for these actions?
* This should be used when a controller or something needs allow
* actions if the user passes in their email and password.
* Is the email/password authorized for these actions? This should be used
* when a controller or something needs allow actions if the user passes in
* their email and password.
*
* It may be better to check this as part of a servlet Filter and
* add an identifier bundle.
* It may be better to check this as part of a servlet Filter and add an
* identifier bundle.
*/
public static boolean isAuthorizedForActions( HttpServletRequest req,
String email, String password,
Actions actions){
if( password == null || email == null ||
password.isEmpty() || email.isEmpty()){
return false;
}
try{
Authenticator auth = Authenticator.getInstance(req);
UserAccount user = auth.getAccountForInternalAuth( email );
log.debug("userAccount is " + user==null?"null":user.getUri() );
if( ! auth.isCurrentPassword( user, password ) ){
log.debug(String.format("UNAUTHORIZED, password not accepted for %s, account URI: %s",
user.getEmailAddress(), user.getUri()));
return false;
}else{
log.debug(String.format("password accepted for %s, account URI: %s",
user.getEmailAddress(), user.getUri() ));
// figure out if that account can do the actions
IdentifierBundle ids =
ActiveIdentifierBundleFactories.getUserIdentifierBundle(req,user);
PolicyIface policy = ServletPolicyList.getPolicies(req);
return PolicyHelper.isAuthorizedForActions( ids, policy, actions );
}
}catch(Exception ex){
log.error("Error while attempting to authorize actions " + actions.toString(), ex);
return false;
}
public static boolean isAuthorizedForActions(HttpServletRequest req,
String email, String password, Actions actions) {
if (password == null || email == null || password.isEmpty()
|| email.isEmpty()) {
return false;
}
try {
Authenticator auth = Authenticator.getInstance(req);
UserAccount user = auth.getAccountForInternalAuth(email);
if (user == null) {
log.debug("No account for '" + email + "'");
return false;
}
String uri = user.getUri();
log.debug("userAccount is '" + uri + "'");
if (!auth.isCurrentPassword(user, password)) {
log.debug(String.format("UNAUTHORIZED, password not accepted "
+ "for %s, account URI: %s", email, uri));
return false;
}
log.debug(String.format("password accepted for %s, "
+ "account URI: %s", email, uri));
// figure out if that account can do the actions
IdentifierBundle ids = ActiveIdentifierBundleFactories
.getUserIdentifierBundle(req, user);
PolicyIface policy = ServletPolicyList.getPolicies(req);
return PolicyHelper.isAuthorizedForActions(ids, policy, actions);
} catch (Exception ex) {
log.error(
"Error while attempting to authorize actions "
+ actions.toString(), ex);
return false;
}
}
/**
* Do the current policies authorize the current user to add this statement
* to this model?
@ -120,8 +125,8 @@ public class PolicyHelper {
}
Resource subject = stmt.getSubject();
edu.cornell.mannlib.vitro.webapp.beans.Property predicate =
new edu.cornell.mannlib.vitro.webapp.beans.Property(stmt.getPredicate().getURI());
edu.cornell.mannlib.vitro.webapp.beans.Property predicate = new edu.cornell.mannlib.vitro.webapp.beans.Property(
stmt.getPredicate().getURI());
RDFNode objectNode = stmt.getObject();
if ((subject == null) || (predicate == null) || (objectNode == null)) {
return false;
@ -130,8 +135,8 @@ public class PolicyHelper {
RequestedAction action;
if (objectNode.isResource()) {
action = new AddObjectPropertyStatement(modelToBeModified,
subject.getURI(), predicate, objectNode
.asResource().getURI());
subject.getURI(), predicate, objectNode.asResource()
.getURI());
} else {
action = new AddDataPropertyStatement(modelToBeModified,
subject.getURI(), predicate.getURI(), objectNode
@ -153,8 +158,7 @@ public class PolicyHelper {
}
Resource subject = stmt.getSubject();
edu.cornell.mannlib.vitro.webapp.beans.Property predicate =
new edu.cornell.mannlib.vitro.webapp.beans.Property();
edu.cornell.mannlib.vitro.webapp.beans.Property predicate = new edu.cornell.mannlib.vitro.webapp.beans.Property();
predicate.setURI(stmt.getPredicate().getURI());
RDFNode objectNode = stmt.getObject();
if ((subject == null) || (predicate == null) || (objectNode == null)) {
@ -164,8 +168,8 @@ public class PolicyHelper {
RequestedAction action;
if (objectNode.isResource()) {
action = new DropObjectPropertyStatement(modelToBeModified,
subject.getURI(), predicate, objectNode
.asResource().getURI());
subject.getURI(), predicate, objectNode.asResource()
.getURI());
} else {
action = new DropDataPropertyStatement(modelToBeModified,
subject.getURI(), predicate.getURI(), objectNode
@ -310,7 +314,6 @@ public class PolicyHelper {
+ stmt.getObject() + ">";
}
/**
* No need to instantiate this helper class - all methods are static.
*/

View file

@ -0,0 +1,22 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.controller.api;
/**
* When you try to use an API that you aren't authorized for, we don't redirect
* you to the login page. We complain.
*/
public class NotAuthorizedToUseApiException extends Exception {
public NotAuthorizedToUseApiException(String message) {
super(message);
}
public NotAuthorizedToUseApiException(Throwable cause) {
super(cause);
}
public NotAuthorizedToUseApiException(String message, Throwable cause) {
super(message, cause);
}
}

View file

@ -3,15 +3,8 @@
package edu.cornell.mannlib.vitro.webapp.filestorage.uploadrequest;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
@ -19,8 +12,6 @@ import javax.servlet.http.HttpServletRequestWrapper;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* <p>
@ -51,41 +42,22 @@ import org.apache.commons.logging.LogFactory;
* implemented in the sub-classes.
* </p>
*/
@SuppressWarnings("deprecation")
public abstract class FileUploadServletRequest extends HttpServletRequestWrapper {
private static final Log log = LogFactory
.getLog(FileUploadServletRequest.class);
public static final String FILE_ITEM_MAP = "file_item_map";
public static final String FILE_UPLOAD_EXCEPTION = "file_upload_exception";
private Map<String, List<String>> parameters;
private Map<String, List<FileItem>> files;
private FileUploadException fileUploadException;
private static final String[] EMPTY_ARRAY = new String[0];
// ----------------------------------------------------------------------
// The factory method
// ----------------------------------------------------------------------
/**
* Wrap this {@link HttpServletRequest} in an appropriate wrapper class.
* set maxTempFileSize to 0 or -1 if streaming is desired. Set it to > 0 if
* you want any parts uploaded to a temporary directory
*/
public static FileUploadServletRequest parseRequest(
HttpServletRequest request, int maxTempFileSize) throws IOException {
public static FileUploadServletRequest parseRequest(
HttpServletRequest request, int maxFileSize) throws IOException {
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
if (isMultipart) {
if( maxTempFileSize <= 0 ){
return new StreamingMultipartHttpServletRequest(request);
}else{
return new MultipartHttpServletRequest(request, maxTempFileSize);
}
return new MultipartHttpServletRequest(request, maxFileSize);
} else {
return new SimpleHttpServletRequestWrapper(request);
}
@ -112,184 +84,29 @@ public abstract class FileUploadServletRequest extends HttpServletRequestWrapper
/** Was this a multipart HTTP request? */
public abstract boolean isMultipart();
protected void stashParametersInRequest(HttpServletRequest request, ServletFileUpload upload){
Map<String, List<String>> parameters = new HashMap<String, List<String>>();
Map<String, List<FileItem>> files = new HashMap<String, List<FileItem>>();
parseQueryString(request.getQueryString(), parameters);
try {
List<FileItem> items = upload.parseRequest( request );
for (FileItem item : items) {
// Process a regular form field
if (item.isFormField()) {
addToParameters(parameters, item.getFieldName(), item
.getString("UTF-8"));
log.debug("Form field (parameter) " + item.getFieldName()
+ "=" + item.getString());
} else {
addToFileItems(files, item);
log.debug("File " + item.getFieldName() + ": "
+ item.getName());
}
}
} catch (FileUploadException e) {
fileUploadException = e;
request.setAttribute(
FileUploadServletRequest.FILE_UPLOAD_EXCEPTION, e);
} catch (UnsupportedEncodingException e) {
log.error("could not convert to UTF-8",e);
}
this.parameters = Collections.unmodifiableMap(parameters);
log.debug("Parameters are: " + this.parameters);
this.files = Collections.unmodifiableMap(files);
log.debug("Files are: " + this.files);
request.setAttribute(FILE_ITEM_MAP, this.files);
}
/**
* Pull any parameters out of the URL.
* Get the map of file items, by name.
*/
private void parseQueryString(String queryString,
Map<String, List<String>> parameters) {
log.debug("Query string is : '" + queryString + "'");
if (queryString != null) {
String[] pieces = queryString.split("&");
for (String piece : pieces) {
int equalsHere = piece.indexOf('=');
if (piece.trim().isEmpty()) {
// Ignore an empty piece.
} else if (equalsHere <= 0) {
// A parameter without a value.
addToParameters(parameters, decode(piece), "");
} else {
// A parameter with a value.
String key = piece.substring(0, equalsHere);
String value = piece.substring(equalsHere + 1);
addToParameters(parameters, decode(key), decode(value));
}
}
}
log.debug("Parameters from query string are: " + parameters);
}
public abstract Map<String, List<FileItem>> getFiles();
/**
* Remove any special URL-style encoding.
* Find a non-empty file item with this name.
*
* @return the first such file item, or <code>null</code> if no matching,
* non-empty items were found.
*/
private String decode(String encoded) {
try {
return URLDecoder.decode(encoded, "UTF-8");
} catch (UnsupportedEncodingException e) {
log.error(e, e);
return encoded;
}
}
public abstract FileItem getFileItem(String string);
/**
* Was there an exception when uploading the file items?
*/
public abstract boolean hasFileUploadException();
/** Either create a new List for the value, or add to an existing List. */
private void addToParameters(Map<String, List<String>> map, String name,
String value) {
if (!map.containsKey(name)) {
map.put(name, new ArrayList<String>());
}
map.get(name).add(value);
}
/** Either create a new List for the file, or add to an existing List. */
private void addToFileItems(Map<String, List<FileItem>> map, FileItem file) {
String name = file.getFieldName();
if (!map.containsKey(name)) {
map.put(name, new ArrayList<FileItem>());
}
map.get(name).add(file);
}
public FileUploadException getFileUploadException() {
return fileUploadException;
}
public boolean hasFileUploadException() {
return fileUploadException != null;
}
// ----------------------------------------------------------------------
// Parameter-related methods won't find anything on the delegate request,
// since parsing consumed the parameters. So we need to look to the parsed
// info for the answers.
// ----------------------------------------------------------------------
@Override
public String getParameter(String name) {
if (parameters.containsKey(name)) {
return parameters.get(name).get(0);
} else {
return null;
}
}
@Override
public Enumeration<?> getParameterNames() {
return Collections.enumeration(parameters.keySet());
}
@Override
public String[] getParameterValues(String name) {
if (parameters.containsKey(name)) {
return parameters.get(name).toArray(EMPTY_ARRAY);
} else {
return null;
}
}
@Override
public Map<String, String[]> getParameterMap() {
Map<String, String[]> result = new HashMap<String, String[]>();
for (Entry<String, List<String>> entry : parameters.entrySet()) {
result.put(entry.getKey(), entry.getValue().toArray(EMPTY_ARRAY));
}
log.debug("resulting parameter map: " + result);
return result;
}
/**
* {@inheritDoc}
* <p>
* There may be more than one file item with the given name. If the first
* one is empty (size is zero), keep looking for a non-empty one.
* </p>
*/
public FileItem getFileItem(String name) {
List<FileItem> items = files.get(name);
if (items == null) {
return null;
}
for (FileItem item : items) {
if (item.getSize() > 0L) {
return item;
}
}
return null;
}
/**
* Gets a map of parameter names to files.
* This will should return null.
*/
public Map<String, List<FileItem>> getFiles() {
if( files == null )
return Collections.emptyMap();
else
return files;
}
/**
* Get the exception that occurred when uploading the file items. If no such
* exception, return <code>null</code>.
*/
public abstract FileUploadException getFileUploadException();
}

View file

@ -29,33 +29,100 @@ import org.apache.commons.logging.LogFactory;
* any parameter-related requests. File-related information will also be held
* here, to answer file-related requests.
*/
public class MultipartHttpServletRequest extends FileUploadServletRequest {
class MultipartHttpServletRequest extends FileUploadServletRequest {
private static final Log log = LogFactory
.getLog(MultipartHttpServletRequest.class);
private int maxFileSize = 0;
private File tempDir = null;
private static final String[] EMPTY_ARRAY = new String[0];
private final Map<String, List<String>> parameters;
private final Map<String, List<FileItem>> files;
private FileUploadException fileUploadException;
/**
* Parse the multipart request. Store the info about the request parameters
* and the uploaded files to a temporary directory.
*
* This offers a simple way to deal with uploaded files by having a size
* limit. This limit may be rather large if desired. Many megabytes for example.
* and the uploaded files.
*/
public MultipartHttpServletRequest(HttpServletRequest request,
int maxFileSize) throws IOException {
super(request);
this.maxFileSize = maxFileSize;
this.tempDir = figureTemporaryDirectory(request);
//use an upload handler that will stash the file items in a temporary directory.
ServletFileUpload upload = createUploadHandler( this.maxFileSize, this.tempDir );
stashParametersInRequest( request , upload );
Map<String, List<String>> parameters = new HashMap<String, List<String>>();
Map<String, List<FileItem>> files = new HashMap<String, List<FileItem>>();
File tempDir = figureTemporaryDirectory(request);
ServletFileUpload upload = createUploadHandler(maxFileSize, tempDir);
parseQueryString(request.getQueryString(), parameters);
try {
List<FileItem> items = parseRequestIntoFileItems(request, upload);
for (FileItem item : items) {
// Process a regular form field
if (item.isFormField()) {
addToParameters(parameters, item.getFieldName(), item
.getString("UTF-8"));
log.debug("Form field (parameter) " + item.getFieldName()
+ "=" + item.getString());
} else {
addToFileItems(files, item);
log.debug("File " + item.getFieldName() + ": "
+ item.getName());
}
}
} catch (FileUploadException e) {
fileUploadException = e;
request.setAttribute(
FileUploadServletRequest.FILE_UPLOAD_EXCEPTION, e);
}
this.parameters = Collections.unmodifiableMap(parameters);
log.debug("Parameters are: " + this.parameters);
this.files = Collections.unmodifiableMap(files);
log.debug("Files are: " + this.files);
request.setAttribute(FILE_ITEM_MAP, this.files);
}
/**
* Pull any parameters out of the URL.
*/
private void parseQueryString(String queryString,
Map<String, List<String>> parameters) {
log.debug("Query string is : '" + queryString + "'");
if (queryString != null) {
String[] pieces = queryString.split("&");
for (String piece : pieces) {
int equalsHere = piece.indexOf('=');
if (piece.trim().isEmpty()) {
// Ignore an empty piece.
} else if (equalsHere <= 0) {
// A parameter without a value.
addToParameters(parameters, decode(piece), "");
} else {
// A parameter with a value.
String key = piece.substring(0, equalsHere);
String value = piece.substring(equalsHere + 1);
addToParameters(parameters, decode(key), decode(value));
}
}
}
log.debug("Parameters from query string are: " + parameters);
}
/**
* Remove any special URL-style encoding.
*/
private String decode(String encoded) {
try {
return URLDecoder.decode(encoded, "UTF-8");
} catch (UnsupportedEncodingException e) {
log.error(e, e);
return encoded;
}
}
/**
* Find the temporary storage directory for this webapp.
*/
@ -63,7 +130,7 @@ public class MultipartHttpServletRequest extends FileUploadServletRequest {
return (File) request.getSession().getServletContext().getAttribute(
"javax.servlet.context.tempdir");
}
/**
* Create an upload handler that will throw an exception if the file is too
* large.
@ -79,10 +146,116 @@ public class MultipartHttpServletRequest extends FileUploadServletRequest {
return upload;
}
@Override
public boolean isMultipart() {
return true;
}
/** Either create a new List for the value, or add to an existing List. */
private void addToParameters(Map<String, List<String>> map, String name,
String value) {
if (!map.containsKey(name)) {
map.put(name, new ArrayList<String>());
}
map.get(name).add(value);
}
/** Either create a new List for the file, or add to an existing List. */
private void addToFileItems(Map<String, List<FileItem>> map, FileItem file) {
String name = file.getFieldName();
if (!map.containsKey(name)) {
map.put(name, new ArrayList<FileItem>());
}
map.get(name).add(file);
}
/** Minimize the code that uses the unchecked cast. */
@SuppressWarnings("unchecked")
private List<FileItem> parseRequestIntoFileItems(HttpServletRequest req,
ServletFileUpload upload) throws FileUploadException {
return upload.parseRequest(req);
}
// ----------------------------------------------------------------------
// This is a multipart request, so make the file info available. If there
// was an exception during parsing, make that available too.
// ----------------------------------------------------------------------
@Override
public boolean isMultipart() {
return true;
}
@Override
public Map<String, List<FileItem>> getFiles() {
return files;
}
/**
* {@inheritDoc}
* <p>
* There may be more than one file item with the given name. If the first
* one is empty (size is zero), keep looking for a non-empty one.
* </p>
*/
@Override
public FileItem getFileItem(String name) {
List<FileItem> items = files.get(name);
if (items == null) {
return null;
}
for (FileItem item : items) {
if (item.getSize() > 0L) {
return item;
}
}
return null;
}
@Override
public FileUploadException getFileUploadException() {
return fileUploadException;
}
@Override
public boolean hasFileUploadException() {
return fileUploadException != null;
}
// ----------------------------------------------------------------------
// Parameter-related methods won't find anything on the delegate request,
// since parsing consumed the parameters. So we need to look to the parsed
// info for the answers.
// ----------------------------------------------------------------------
@Override
public String getParameter(String name) {
if (parameters.containsKey(name)) {
return parameters.get(name).get(0);
} else {
return null;
}
}
@Override
public Enumeration<?> getParameterNames() {
return Collections.enumeration(parameters.keySet());
}
@Override
public String[] getParameterValues(String name) {
if (parameters.containsKey(name)) {
return parameters.get(name).toArray(EMPTY_ARRAY);
} else {
return null;
}
}
@Override
public Map<String, String[]> getParameterMap() {
Map<String, String[]> result = new HashMap<String, String[]>();
for (Entry<String, List<String>> entry : parameters.entrySet()) {
result.put(entry.getKey(), entry.getValue().toArray(EMPTY_ARRAY));
}
log.debug("resulting parameter map: " + result);
return result;
}
}

View file

@ -33,7 +33,26 @@ class SimpleHttpServletRequestWrapper extends FileUploadServletRequest {
return false;
}
@Override
public Map<String, List<FileItem>> getFiles() {
return Collections.emptyMap();
}
@Override
public FileItem getFileItem(String string) {
return null;
}
@Override
public FileUploadException getFileUploadException() {
return null;
}
@Override
public boolean hasFileUploadException() {
return false;
}
// ----------------------------------------------------------------------
// Since this is not a multipart request, the parameter methods can be
// delegated.
@ -45,7 +64,7 @@ class SimpleHttpServletRequestWrapper extends FileUploadServletRequest {
}
@Override
public Map<String, String[]> getParameterMap() {
public Map<?, ?> getParameterMap() {
return getDelegate().getParameterMap();
}

View file

@ -1,38 +0,0 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.filestorage.uploadrequest;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
/**
* Wrapping ServletRequest that does multipart. In order to allow
* streaming, this class does NOT save the parts to a temporary directory.
*
*
*/
public class StreamingMultipartHttpServletRequest extends
FileUploadServletRequest {
/**
* Parse the multipart request. Store the info about the request parameters.
* Don't store the uploaded files to a temporary directory to allow streaming.
*
* Only use this if you plan to consume the FileItems using streaming
* to deal with inputs of very large sizes.
*
*/
public StreamingMultipartHttpServletRequest(HttpServletRequest request) {
super(request);
//use a file uploader that does not save the files to a temporary directory.
stashParametersInRequest( request , new ServletFileUpload());
}
@Override
public boolean isMultipart() {
return true;
}
}

View file

@ -2,7 +2,12 @@
package edu.cornell.mannlib.vitro.webapp.search.controller;
import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@ -10,124 +15,130 @@ import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.vitro.webapp.auth.identifier.ActiveIdentifierBundleFactories;
import edu.cornell.mannlib.vitro.webapp.auth.identifier.IdentifierBundle;
import edu.cornell.mannlib.vitro.webapp.auth.permissions.SimplePermission;
import edu.cornell.mannlib.vitro.webapp.auth.policy.PolicyHelper;
import edu.cornell.mannlib.vitro.webapp.auth.policy.ServletPolicyList;
import edu.cornell.mannlib.vitro.webapp.auth.policy.ifaces.PolicyIface;
import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.Actions;
import edu.cornell.mannlib.vitro.webapp.beans.UserAccount;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.controller.authenticate.Authenticator;
import edu.cornell.mannlib.vitro.webapp.controller.authenticate.BasicAuthenticator;
import edu.cornell.mannlib.vitro.webapp.controller.api.NotAuthorizedToUseApiException;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.FreemarkerHttpServlet;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ExceptionResponseValues;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.TemplateResponseValues;
import edu.cornell.mannlib.vitro.webapp.filestorage.uploadrequest.FileUploadServletRequest;
import edu.cornell.mannlib.vitro.webapp.filestorage.uploadrequest.MultipartHttpServletRequest;
import edu.cornell.mannlib.vitro.webapp.search.indexing.IndexBuilder;
/**
* Accepts requests to update a set of URIs in the search index.
* Accepts requests to update a set of URIs in the search index.
*/
@SuppressWarnings("serial")
public class SearchServiceController extends FreemarkerHttpServlet {
private static final Log log = LogFactory.getLog(SearchServiceController.class);
private static final Log log = LogFactory
.getLog(SearchServiceController.class);
/**
* Attempt to check if there is a email and password in the request parameters.
* If there is not, fall back on the normal usage pattern of requestedAction().
* If there is, then try to authenticate and authorize the
* userAccount associated with the email.
*/
@Override
public Actions requiredActions(VitroRequest vreq) {
try{
// Works by side effect: parse the multi-part request and stash FileItems in request
FileUploadServletRequest.parseRequest(vreq, 0);
/** Limit file size to 1 Gigabyte. */
public static final int MAXIMUM_FILE_SIZE = 1024 * 1024 * 1024;
//first figure out if the password and email login to an account with a password
String pw = vreq.getParameter("password");
String email = vreq.getParameter("email");
if( pw == null || email == null || pw.isEmpty() || email.isEmpty()){
return SimplePermission.MANAGE_SEARCH_INDEX.ACTIONS;
}
if( PolicyHelper.isAuthorizedForActions(vreq, email, pw,
SimplePermission.MANAGE_SEARCH_INDEX.ACTIONS ) ){
return Actions.AUTHORIZED;
}else{
log.debug(email + " is unauthorized to manage the search index. " +
"client IP "+vreq.getClientAddr());
return Actions.UNAUTHORIZED;
}
}catch(Exception ex){
log.error("Error while client IP "+ vreq.getClientAddr() + " attempting to log in " +
"to SearchServiceController: " + ex.getMessage());
return Actions.UNAUTHORIZED;
}
}
/**
* Handle the different actions. If not specified, the default action is to
* show the help page.
*/
*/
@Override
protected ResponseValues processRequest(VitroRequest req) {
try {
//figure out what action to perform
String pathInfo = req.getPathInfo();
if( pathInfo == null || pathInfo.trim().isEmpty() || "/".equals(pathInfo.trim()) ){
return doHelpForm(req);
}
pathInfo = pathInfo.substring(1); //get rid of leading slash
if (VERBS.UPDATE_URIS_IN_SEARCH.verb.equals( pathInfo )) {
req = new VitroRequest(FileUploadServletRequest.parseRequest(req,
MAXIMUM_FILE_SIZE));
// Check the authorization here, because we don't want to redirect
// to the login page if they are not authorized. (The file upload
// would be lost.
confirmAuthorization(req);
switch (figureActionVerb(req)) {
case UPDATE_URIS_IN_SEARCH:
return doUpdateUrisInSearch(req);
} else {
return doHelpForm(req);
default:
return doHelpForm();
}
} catch (NotAuthorizedToUseApiException e) {
Map<String, Object> map = new HashMap<>();
map.put("errorMessage", e.getMessage());
return new TemplateResponseValues("error-message.ftl", map,
SC_FORBIDDEN);
} catch (Exception e) {
return new ExceptionResponseValues(e);
return new ExceptionResponseValues(e, SC_INTERNAL_SERVER_ERROR);
}
}
/**
* Process requests to the web service to update a list of URIs in the
* search index.
*/
private ResponseValues doUpdateUrisInSearch(HttpServletRequest req)
throws IOException, ServletException {
IndexBuilder builder = IndexBuilder.getBuilder(getServletContext());
int uriCount = new UpdateUrisInIndex().doUpdateUris(req, builder);
/**
* Process requests to the web service to update a list of URIs in the search index. */
public ResponseValues doUpdateUrisInSearch(HttpServletRequest req )
throws IOException, ServletException {
IndexBuilder builder = IndexBuilder.getBuilder(getServletContext());
if( builder == null )
throw new ServletException( "Could not get search index builder from context. Check smoke test");
Map<String, Object> body = new HashMap<>();
body.put("msg", "Received " + uriCount + " URIs.");
new UpdateUrisInIndex().doUpdateUris( req, builder);
return new TemplateResponseValues("searchService-help.ftl", body);
}
TemplateResponseValues trv = new TemplateResponseValues( "" );
return trv;
}
private ResponseValues doHelpForm() {
return new TemplateResponseValues("searchService-help.ftl");
}
public ResponseValues doHelpForm(HttpServletRequest req){
return new TemplateResponseValues( "searchService-help.ftl");
}
public enum VERBS{
UPDATE_URIS_IN_SEARCH("updateUrisInSearch");
public final String verb;
VERBS(String verb){
this.verb = verb;
}
}
private void confirmAuthorization(VitroRequest vreq)
throws NotAuthorizedToUseApiException {
Verb verb = figureActionVerb(vreq);
String pw = vreq.getParameter("password");
String email = vreq.getParameter("email");
// If you just want the help screen, no problem.
if (verb == Verb.VIEW_HELP_FORM) {
return;
}
// For other functions, your credentials must have moxie.
if (PolicyHelper.isAuthorizedForActions(vreq, email, pw,
SimplePermission.MANAGE_SEARCH_INDEX.ACTIONS)) {
return;
}
// Otherwise, you can't do this.
throw new NotAuthorizedToUseApiException(email
+ " is not authorized to manage the search index.");
}
private Verb figureActionVerb(VitroRequest vreq) {
String pathInfo = vreq.getPathInfo();
if (pathInfo == null) {
pathInfo = "";
}
if (pathInfo.startsWith("/")) {
pathInfo = pathInfo.substring(1);
}
return Verb.fromString(pathInfo);
}
// ----------------------------------------------------------------------
// Helper class
// ----------------------------------------------------------------------
public enum Verb {
VIEW_HELP_FORM("viewHelpForm"), UPDATE_URIS_IN_SEARCH(
"updateUrisInSearch");
public final String verb;
Verb(String verb) {
this.verb = verb;
}
static Verb fromString(String s) {
for (Verb v : values()) {
if (v.verb.equals(s)) {
return v;
}
}
return VIEW_HELP_FORM;
}
}
}

View file

@ -2,191 +2,101 @@
package edu.cornell.mannlib.vitro.webapp.search.controller;
import java.io.BufferedReader;
import static edu.cornell.mannlib.vitro.webapp.filestorage.uploadrequest.FileUploadServletRequest.FILE_ITEM_MAP;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.regex.Pattern;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.vitro.webapp.search.indexing.IndexBuilder;
/**
* Class that performs the update of the uris in the search index
* for the SearchService.
* Class that performs the update of the uris in the search index for the
* SearchService.
*/
public class UpdateUrisInIndex {
private static final Log log = LogFactory.getLog(UpdateUrisInIndex.class);
/**
* Web service for update in search index of a list of URIs.
* @throws IOException
*/
protected void doUpdateUris(HttpServletRequest req, IndexBuilder builder)
throws ServletException, IOException{
if( ! ServletFileUpload.isMultipartContent(req) )
throw new ServletException("Expected Multipart Content");
String enc = getEncoding(req);
try{
//loop over the fileds and add any URIs to the IndexBuilder queue
ServletFileUpload upload = new ServletFileUpload();
FileItemIterator iter = upload.getItemIterator(req);
while( iter.hasNext()){
FileItemStream item = iter.next();
String name = item.getFieldName();
if( "email".equals(name) || "password".equals(name) )
continue; //skip the password and email fields
private static final Log log = LogFactory.getLog(UpdateUrisInIndex.class);
InputStream stream = item.openStream();
try{
addToSearchQueue(builder, stream, enc);
}finally{
stream.close();
}
}
}catch (FileUploadException fex){
throw new ServletException("Could not upload file to SearchServiceController", fex);
}finally{
builder.doUpdateIndex();
}
}
/** Pattern to split URIs on whitespace and commas. */
public static final Pattern DELIMITER = Pattern.compile("[,\\s]+");
/**
* Get the encoding of the request, default to UTF-8
* since that is in the vitro install instructions
* to put on the connector.
*/
private String getEncoding(HttpServletRequest req){
String enc = req.getCharacterEncoding();
if( enc == null || enc.isEmpty() ){
log.debug("No encoding on POST request, That is acceptable.");
enc = "UTF-8";
}else if( enc.length() > 30){
log.debug("Ignoring odd encoding of '" + enc + "'");
enc = "UTF-8";
}else{
log.debug("Encoding set on POST request: " + enc);
}
log.debug("Reading POSTed URIs with encoding " + enc);
return enc;
}
/**
* Web service for update in search index of a list of URIs.
*
* @throws IOException
*/
protected int doUpdateUris(HttpServletRequest req, IndexBuilder builder)
throws ServletException, IOException {
@SuppressWarnings("unchecked")
Map<String, List<FileItem>> map = (Map<String, List<FileItem>>) req
.getAttribute(FILE_ITEM_MAP);
if (map == null) {
throw new ServletException("Expected Multipart Content");
}
Charset enc = getEncoding(req);
/**
* Adds URIs from Reader to search queue.
*/
private void addToSearchQueue( IndexBuilder builder, InputStream stream , String charEncoding )
throws IOException{
int uriCount = 0;
for (String name : map.keySet()) {
for (FileItem item : map.get(name)) {
log.debug("Found " + item.getSize() + " byte file for '" + name + "'");
uriCount += processFileItem(builder, name, item, enc);
}
}
return uriCount;
}
Iterator<String> uris =
new UrisFromInputIterator( new InputStreamReader(stream, charEncoding) );
private int processFileItem(IndexBuilder builder, String name,
FileItem item, Charset enc) throws IOException {
int count = 0;
Reader reader = new InputStreamReader(item.getInputStream(), enc.name());
try (Scanner scanner = createScanner(reader)) {
while (scanner.hasNext()) {
String uri = scanner.next();
log.debug("Request to index uri '" + uri + "'");
builder.addToChanged(uri);
count++;
}
}
return count;
}
while(uris.hasNext()){
String uri = uris.next();
log.debug("Request to index uri '" + uri + "'");
builder.addToChanged( uri );
}
}
@SuppressWarnings("resource")
protected Scanner createScanner(Reader in) {
return new Scanner(in).useDelimiter(DELIMITER);
}
/**
* Iterator for URIs in a reader to make top level methods simpler.
*/
public static class UrisFromInputIterator implements Iterator<String> {
BufferedReader reader;
Iterator<String> uris;
public UrisFromInputIterator(Reader in ){
this.reader = new BufferedReader(in);
}
public void remove(){ throw new UnsupportedOperationException() ; }
public boolean hasNext(){
if( uris != null && uris.hasNext() ){
return true;
}else{
try {
return getFromBuffer();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return false;
}
}
}
public String next(){
return uris.next();
}
/** Returns true if there are uris to get.
* @throws IOException */
private boolean getFromBuffer() throws IOException{
uris = null;
while( uris == null || !uris.hasNext() ){
String chunk = reader.readLine();
if( chunk == null ){ //at end of input
break;
} else if( chunk.trim().isEmpty() ){
continue;
}else{
uris = lineToUris(chunk).iterator();
if( uris.hasNext() ){
return true;
}
}
}
return false;
}
}
/**
* Removes null and empty elements from in.
* Returned list will not be null.
*/
private static List<String> removeNullAndEmpty(List<String> in ){
ArrayList<String> out = new ArrayList<String>();
if( in == null )
return out;
for( String s : in ){
if( s != null && !s.trim().isEmpty() ){
out.add(s);
}
}
return out;
}
/**
* Parses a line to a list of URIs.
* Retruned list will not be null.
* No elements in returned list will be empty or null.
*/
protected static List<String> lineToUris(String line){
List<String> parts = removeNullAndEmpty( Arrays.asList(commaAndWhitespace.split( line ) ));
return parts;
}
/** Pattern to split URIs on whitespace and commas. */
private static final Pattern commaAndWhitespace = Pattern.compile("[,\\s]");
/**
* Get the encoding of the request, default to UTF-8 since that is in the
* vitro install instructions to put on the connector.
*/
private Charset getEncoding(HttpServletRequest req) {
String enc = req.getCharacterEncoding();
if (StringUtils.isBlank(enc)) {
log.debug("No encoding on POST request, That is acceptable.");
enc = "UTF-8";
} else if (enc.length() > 30) {
log.debug("Ignoring odd encoding of '" + enc + "'");
enc = "UTF-8";
} else {
log.debug("Encoding set on POST request: " + enc);
}
log.debug("Reading POSTed URIs with encoding " + enc);
return Charset.forName(enc);
}
}