Package net.sf.collabreview.web

Source Code of net.sf.collabreview.web.PluginService$SessionAuthentication

/*
   Copyright 2012 Christian Prause and Fraunhofer FIT

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package net.sf.collabreview.web;

import net.sf.collabreview.core.ArtifactIdentifier;
import net.sf.collabreview.core.CollabReviewSingleton;
import net.sf.collabreview.core.users.Author;
import net.sf.collabreview.repository.Review;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import prause.toolbox.other.Stacktrace;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
* Provides assessment web service API to IDE plugins.
* Handles only one request at a time/not thread safe.
* <p/>
* <h1>Web service API description</h1>
* When you call the plugin API web service, there are several parameters that must always be set:
* <ul>
* <li><b>command</b>: The command that is to be performed by the web service. Possible values are: "load" and "save"</li>
* <li><b>author</b>: The user id of the user who is accessing the web service</li>
* <li><b>password</b>: The user's password</li>
* <li><b>document</b>: The document that is to be accessed by the user. This string may be the local file name of the file. The web service will try to translate this string into the ID of a document.</li>
* </ul>
* <p/>
* Depending on the command (either load or save) the following additional parameters are required:
* <h2>load command</h2>
* no additional parameters required
* <h2>save command</h2>
* <ul>
* <li><b>text</b>: a string with the comments that a reviewer wants to submit</li>
* <li><b>rating</b>: an integer from -10 (bad) to +10 (good) describing the rating</li>
* <li><b>anonymous</b>: one of {true,false} denoting if the review is submitted anonymously</li>
* </ul>
* <p/>
* <h2>Web service response</h2>
* The web service responds with a MIME "text/plain" answer.
* The first line of the response contains a version number (only 1.0 at the moment) that is followed by a single \n character.
* After this there will be several key-value pairs encoded as a Java Properties file:
* <ul>
* <li>Encoding is ISO 8859-1</li>
* <li>Lines beginning with '#' are comments</li>
* <li>Every line is a key-value pair</li>
* <li>Every line contains an equals character</li>
* <li>Left of '=' is the key</li>
* <li>Right of '=' is the value. Special characters (like line breaks) will be escaped, like: \n means line break </li>
* <li>There is no guarantee for an order in which key-value pairs occur</li>
* </ul>
* <p/>
* Every response contains a status code stored in the "status" property. The status is either "ok" or "error".
* Error responses will have "error" and "cause" properties set to a string describing the error. "cause" is a stack
* trace.
* <p/>
* Successful responses to the "store" command do currently not contain any further information.
* A response to a successful "load" command, accessing a non-existent review, will also not contain any further information.
* If the review exists, the "load" response will contain a string with the recent review text by this author,
* the rating (an integer from -10 to +10) and one of {true,false} for the "anonymous" property.
*
* @author Christian Prause (chris)
* @date 15.11.2007 16:21:25
*/
public class PluginService extends HttpServlet {
  private static final Log logger = LogFactory.getLog(PluginService.class);

  private final static String SESSION_AUTHENTICATION_ATTRIBUTE_NAME = "CRWebServiceAuthentication";

  public enum Command {
    load,
    save
  }

  public PluginService() {
  }

  protected synchronized void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    logger.debug("Servicing web service call");
    if (request.getParameter("command") == null) {
      returnInfoPage(response);
      return;
    }
   
    try {
      writeResponse(response, handle(request));
    } catch (Exception e) {
      logger.error("Error handling request: ", e);
      Properties errorResponse = new Properties();
      errorResponse.setProperty("status", "error");
      errorResponse.setProperty("error", e.getMessage());
      errorResponse.setProperty("cause", Stacktrace.getString(e));
      writeResponse(response, errorResponse);
    }
  }

  private void writeResponse(HttpServletResponse response, Properties props) throws IOException {
    // prepare data
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    props.store(baos, "CollabReview WebService response");
    byte[] data = baos.toByteArray();
    // set headers
    response.setContentType("text/plain");
    response.setStatus(HttpServletResponse.SC_OK);
    response.setContentLength(data.length);
    // send data
    ServletOutputStream os = response.getOutputStream();
    os.print("1.0\n");
    os.write(data);
    os.flush();
  }

  private Properties handle(HttpServletRequest request) {
    Properties result = new Properties();
    result.setProperty("status", "ok");
    Command command = Command.valueOf(request.getParameter("command"));
    logger.debug("Command: " + command);
    if (command == null) {
      throw new IllegalArgumentException("Illegal null command");
    }
    // check author is non-null
    Author author = getAuthorAndCheckAuthentication(request);
    // identify target resource
    ArtifactIdentifier target = identifyResource(request);
    if (target == null) {
      throw new IllegalArgumentException("document not found");
    }
    switch (command) {
      case load:
        Review review = CollabReviewSingleton.get().getRepository().getReview(target.getName(), target.getBranch(), author);
        if (review != null) {
          result.setProperty("rating", "" + review.getRating());
          result.setProperty("text", review.getReviewText());
          result.setProperty("anonymous", "" + review.getIsAnonymous());
        }
        break;
      case save:
        String text = request.getParameter("text");
        int rating = Integer.parseInt(request.getParameter("rating"));
        boolean anonymous = Boolean.parseBoolean(request.getParameter("anonymous"));
        if (text == null) {
          throw new IllegalArgumentException("no text");
        }
        CollabReviewSingleton.get().getRepository().setReview(target, author, rating, text, anonymous);
        break;
      default:
        throw new IllegalArgumentException("Internal error, unknown command");
    }
    return result;
  }

  private Author getAuthorAndCheckAuthentication(HttpServletRequest request) {
    String accountName = request.getParameter("account");
    String password = request.getParameter("password");
    SessionAuthentication auth = (SessionAuthentication) request.getSession().getAttribute(SESSION_AUTHENTICATION_ATTRIBUTE_NAME);
    Author author;
    if (accountName == null || password == null) {
      if (auth == null) {
        logger.debug("login data missing");
        throw new IllegalArgumentException("login data missing");
      }
      author = CollabReviewSingleton.get().getAuthorManager().getAuthor(auth.getUserName());
      author.authenticate(auth.getPassword());
    } else {
      // wait a moment to reduce risk of brute force attacks
      try {
        Thread.sleep(500);
      } catch (InterruptedException e) {
      }
      logger.debug("Login attempt: " + accountName);
      author = CollabReviewSingleton.get().getAuthorManager().getAuthor(accountName);
      author.authenticate(password);
      auth = new SessionAuthentication(accountName, password);
    }
    if (!author.isAuthenticated()) {
      logger.warn("Authentication failed for user " + accountName);
      throw new IllegalArgumentException("login failed");
    }
    request.getSession().setAttribute(SESSION_AUTHENTICATION_ATTRIBUTE_NAME, auth);
    return author;
  }

  /**
   * Tries to guess which resource was rated by the IDE plugin.
   * The local name (on the client side) will most probably be different from the name that we have here: it will
   * have a different path and may even come from a different operating system ('/' vs '\').
   * <p/>
   * We try to tackle this problem by first looking only at the name of the file. If this is already unique, we are
   * done. If not, we will look at the name of its directory and so on. If everything fails (more than one candidate
   * remains or no candidate at all, we return an error).
   *
   * @param request the request with the local document path in it
   * @return the artifact identifier for the most probable artifact
   */
  private ArtifactIdentifier identifyResource(HttpServletRequest request) {
    String docName = request.getParameter("document");
    logger.debug("Identifying resource: " + docName);
    if (docName == null) {
      throw new NullPointerException("request parameter \"document\" not found");
    }
    String[] localNameParts = getNameParts(docName);
    List<ArtifactIdentifier> candidates = new ArrayList<ArtifactIdentifier>(CollabReviewSingleton.get().getRepository().listNonObsoleteArtifacts(null));
    for (int i = 0; ; i++) {
      //logger.debug("Round: " + i);
      candidates = scanCandidates(candidates, localNameParts, i);
      if (candidates.size() == 1) {
        return candidates.get(0);
      }
    }
  }

  private ArrayList<ArtifactIdentifier> scanCandidates(List<ArtifactIdentifier> candidates, String[] localNameParts, int depth) {
    ArrayList<ArtifactIdentifier> winners = new ArrayList<ArtifactIdentifier>();
    if (localNameParts.length > depth) {
      for (ArtifactIdentifier aid : candidates) {
        String[] candidateNameParts = getNameParts(aid.getName());
        if (candidateNameParts.length <= depth) {
          continue;
        }
        //logger.debug("comparing " + localNameParts[depth] + " to " + getNameParts(doc.getId())[depth]);
        if (localNameParts[depth].equals(candidateNameParts[depth])) {
          winners.add(aid);
        }
      }
    } else {
      logger.debug("localNameParts too short: " + localNameParts.length);
    }
    if (winners.size() == 0) {
      throw new IllegalArgumentException("Cannot identify document, there is no match in round " + depth);
    }
    logger.debug("Winners: " + winners.size());
    return winners;
  }

  private String[] getNameParts(String name) {
    String[] nameParts = name.split("[/\\\\]");
    // reverse order of name parts
    for (int i = 0; i < nameParts.length / 2; i++) {
      String swap = nameParts[nameParts.length - i - 1];
      nameParts[nameParts.length - i - 1] = nameParts[i];
      nameParts[i] = swap;
    }
    return nameParts;
  }

  private void returnInfoPage(HttpServletResponse response) throws IOException {
    response.setStatus(HttpServletResponse.SC_OK);
    response.setContentType("text/html");
    response.setCharacterEncoding("utf-8");
    PrintWriter out = response.getWriter();
    out.println("<html>");
    out.println("<head><title>CollabReview Simple Web Services</title></head>");
    out.println("<body>");
    out.println("This is the WebService interface for CollabReview plugins.<br/>");
    out.println("No command specified.");
    out.println("</body>");
    out.println("</html>");
  }

  private static class SessionAuthentication {
    private String userName;
    private String password;

    private SessionAuthentication(String userName, String password) {
      this.userName = userName;
      this.password = password;
    }

    public String getUserName() {
      return userName;
    }

    public String getPassword() {
      return password;
    }
  }
}
TOP

Related Classes of net.sf.collabreview.web.PluginService$SessionAuthentication

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.