mirror of
https://github.com/sismics/docs.git
synced 2025-12-13 01:36:18 +00:00
Initial commit
This commit is contained in:
130
docs-web-common/pom.xml
Normal file
130
docs-web-common/pom.xml
Normal file
@@ -0,0 +1,130 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
|
||||
<parent>
|
||||
<groupId>com.sismics.docs</groupId>
|
||||
<artifactId>docs-parent</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<relativePath>../docs-parent</relativePath>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>docs-web-common</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Docs Web Commons</name>
|
||||
|
||||
<dependencies>
|
||||
<!-- Dependencies to Docs -->
|
||||
<dependency>
|
||||
<groupId>com.sismics.docs</groupId>
|
||||
<artifactId>docs-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Dependencies to Jersey -->
|
||||
<dependency>
|
||||
<groupId>com.sun.jersey</groupId>
|
||||
<artifactId>jersey-server</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.sun.jersey</groupId>
|
||||
<artifactId>jersey-bundle</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.sun.jersey</groupId>
|
||||
<artifactId>jersey-json</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.sun.jersey.contribs</groupId>
|
||||
<artifactId>jersey-multipart</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Other external dependencies -->
|
||||
<dependency>
|
||||
<groupId>commons-lang</groupId>
|
||||
<artifactId>commons-lang</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>log4j</groupId>
|
||||
<artifactId>log4j</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>commons-dbcp</groupId>
|
||||
<artifactId>commons-dbcp</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mortbay.jetty</groupId>
|
||||
<artifactId>servlet-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>joda-time</groupId>
|
||||
<artifactId>joda-time</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Test dependencies -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.sun.grizzly</groupId>
|
||||
<artifactId>grizzly-servlet-webserver</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.sun.jersey.jersey-test-framework</groupId>
|
||||
<artifactId>jersey-test-framework-grizzly2</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.hsqldb</groupId>
|
||||
<artifactId>hsqldb</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.subethamail</groupId>
|
||||
<artifactId>subethasmtp-wiser</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<!-- Install test jar -->
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>test-jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.sismics.rest.exception;
|
||||
|
||||
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
|
||||
import org.codehaus.jettison.json.JSONException;
|
||||
import org.codehaus.jettison.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Jersey exception encapsulating an error from the client (BAD_REQUEST).
|
||||
*
|
||||
* @author jtremeaux
|
||||
*/
|
||||
public class ClientException extends WebApplicationException {
|
||||
/**
|
||||
* Serial UID.
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Logger.
|
||||
*/
|
||||
private static final Logger log = LoggerFactory.getLogger(ClientException.class);
|
||||
|
||||
/**
|
||||
* Constructor of ClientException.
|
||||
*
|
||||
* @param type Error type (e.g. AlreadyExistingEmail, ValidationError)
|
||||
* @param message Human readable error message
|
||||
* @param e Readable error message
|
||||
* @throws JSONException
|
||||
*/
|
||||
public ClientException(String type, String message, Exception e) throws JSONException {
|
||||
this(type, message);
|
||||
log.error(type + ": " + message, e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor of ClientException.
|
||||
*
|
||||
* @param type Error type (e.g. AlreadyExistingEmail, ValidationError)
|
||||
* @param message Human readable error message
|
||||
* @throws JSONException
|
||||
*/
|
||||
public ClientException(String type, String message) throws JSONException {
|
||||
super(Response.status(Status.BAD_REQUEST).entity(new JSONObject()
|
||||
.put("type", type)
|
||||
.put("message", message)).build());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.sismics.rest.exception;
|
||||
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
|
||||
import org.codehaus.jettison.json.JSONException;
|
||||
import org.codehaus.jettison.json.JSONObject;
|
||||
|
||||
/**
|
||||
* Unauthorized access to the resource exception.
|
||||
*
|
||||
* @author jtremeaux
|
||||
*/
|
||||
public class ForbiddenClientException extends WebApplicationException {
|
||||
/**
|
||||
* Serial UID.
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Constructor of ForbiddenClientException.
|
||||
*
|
||||
* @throws JSONException
|
||||
*/
|
||||
public ForbiddenClientException() throws JSONException {
|
||||
super(Response.status(Status.FORBIDDEN).entity(new JSONObject()
|
||||
.put("type", "ForbiddenError")
|
||||
.put("message", "You don't have access to this resource")).build());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.sismics.rest.exception;
|
||||
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.codehaus.jettison.json.JSONException;
|
||||
import org.codehaus.jettison.json.JSONObject;
|
||||
|
||||
/**
|
||||
* Jersey exception encapsulating an error from the client (INTERNAL_SERVER_ERROR).
|
||||
*
|
||||
* @author jtremeaux
|
||||
*/
|
||||
public class ServerException extends WebApplicationException {
|
||||
/**
|
||||
* Serial UID.
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Logger.
|
||||
*/
|
||||
private static final Logger log = LoggerFactory.getLogger(ServerException.class);
|
||||
|
||||
/**
|
||||
* Constructor of ClientException.
|
||||
*
|
||||
* @param type Error type (e.g. DatabaseError)
|
||||
* @param message Human readable error message
|
||||
* @param e Inner exception
|
||||
* @throws JSONException
|
||||
*/
|
||||
public ServerException(String type, String message, Exception e) throws JSONException {
|
||||
this(type, message);
|
||||
log.error(type + ": " + message, e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor of ClientException.
|
||||
*
|
||||
* @param type Error type (e.g. DatabaseError)
|
||||
* @param message Human readable error message
|
||||
* @throws JSONException
|
||||
*/
|
||||
public ServerException(String type, String message) throws JSONException {
|
||||
super(Response.status(Status.INTERNAL_SERVER_ERROR).entity(new JSONObject()
|
||||
.put("type", type)
|
||||
.put("message", message)).build());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.sismics.rest.resource;
|
||||
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.ext.ExceptionMapper;
|
||||
import javax.ws.rs.ext.Provider;
|
||||
|
||||
import org.codehaus.jettison.json.JSONException;
|
||||
import org.codehaus.jettison.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Generic exception mapper that transforms all unknown exception into ServerError.
|
||||
*
|
||||
* @author jtremeaux
|
||||
*/
|
||||
@Provider
|
||||
public class GenericExceptionMapper implements ExceptionMapper<Exception> {
|
||||
/**
|
||||
* Logger.
|
||||
*/
|
||||
private static final Logger log = LoggerFactory.getLogger(GenericExceptionMapper.class);
|
||||
|
||||
@Override
|
||||
public Response toResponse(Exception e) {
|
||||
if (e instanceof WebApplicationException) {
|
||||
return ((WebApplicationException) e).getResponse();
|
||||
}
|
||||
|
||||
log.error("Unknown error", e);
|
||||
|
||||
JSONObject entity = new JSONObject();
|
||||
try {
|
||||
entity.put("type", "UnknownError");
|
||||
entity.put("message", "Unknown server error");
|
||||
} catch (JSONException e2) {
|
||||
log.error("Error building response", e2);
|
||||
}
|
||||
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity(entity)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.sismics.rest.util;
|
||||
|
||||
import org.codehaus.jettison.json.JSONArray;
|
||||
import org.codehaus.jettison.json.JSONException;
|
||||
import org.codehaus.jettison.json.JSONObject;
|
||||
|
||||
/**
|
||||
* JSON utilities.
|
||||
*
|
||||
* @author jtremeaux
|
||||
*/
|
||||
public class JsonUtil {
|
||||
|
||||
/**
|
||||
* Fix of {@see JsonObject.append()}, which seems to create nested arrays.
|
||||
*
|
||||
* @param o JSON Object
|
||||
* @param key Key containing the array of null
|
||||
* @param value Value to append
|
||||
* @return Updated object
|
||||
* @throws JSONException
|
||||
*/
|
||||
public static JSONObject append(JSONObject o, String key, JSONObject value) throws JSONException {
|
||||
Object prevValue = o.opt(key);
|
||||
if (prevValue == null) {
|
||||
o.put(key, new JSONArray().put(value));
|
||||
} else if (!(prevValue instanceof JSONArray)){
|
||||
throw new JSONException("JSONObject[" + key + "] is not a JSONArray.");
|
||||
} else {
|
||||
JSONArray newArray = new JSONArray();
|
||||
JSONArray oldArray = ((JSONArray) prevValue);
|
||||
for (int i = 0; i < oldArray.length(); i++) {
|
||||
newArray.put(oldArray.get(i));
|
||||
}
|
||||
newArray.put(value);
|
||||
o.put(key, newArray);
|
||||
}
|
||||
return o;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
package com.sismics.rest.util;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.codehaus.jettison.json.JSONException;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.sismics.docs.core.dao.file.theme.ThemeDao;
|
||||
import com.sismics.docs.core.dao.jpa.LocaleDao;
|
||||
import com.sismics.docs.core.model.jpa.Locale;
|
||||
import com.sismics.rest.exception.ClientException;
|
||||
|
||||
/**
|
||||
* Utility class to validate parameters.
|
||||
*
|
||||
* @author jtremeaux
|
||||
*/
|
||||
public class ValidationUtil {
|
||||
private static Pattern EMAIL_PATTERN = Pattern.compile(".+@.+\\..+");
|
||||
|
||||
private static Pattern HTTP_URL_PATTERN = Pattern.compile("https?://.+");
|
||||
|
||||
private static Pattern ALPHANUMERIC_PATTERN = Pattern.compile("[a-zA-Z0-9_]+");
|
||||
|
||||
/**
|
||||
* Checks that the argument is not null.
|
||||
*
|
||||
* @param s Object tu validate
|
||||
* @param name Name of the parameter
|
||||
* @throws JSONException
|
||||
*/
|
||||
public static void validateRequired(Object s, String name) throws JSONException {
|
||||
if (s == null) {
|
||||
throw new ClientException("ValidationError", MessageFormat.format("{0} must be set", name));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a string length.
|
||||
*
|
||||
* @param s String to validate
|
||||
* @param name Name of the parameter
|
||||
* @param lengthMin Minimum length (or null)
|
||||
* @param lengthMax Maximum length (or null)
|
||||
* @param nullable True if the string can be empty or null
|
||||
* @return String without white spaces
|
||||
* @throws ClientException
|
||||
*/
|
||||
public static String validateLength(String s, String name, Integer lengthMin, Integer lengthMax, boolean nullable) throws JSONException {
|
||||
s = StringUtils.strip(s);
|
||||
if (nullable && StringUtils.isEmpty(s)) {
|
||||
return s;
|
||||
}
|
||||
if (s == null) {
|
||||
throw new ClientException("ValidationError", MessageFormat.format("{0} must be set", name));
|
||||
}
|
||||
if (lengthMin != null && s.length() < lengthMin) {
|
||||
throw new ClientException("ValidationError", MessageFormat.format("{0} must be more than {1} characters", name, lengthMin));
|
||||
}
|
||||
if (lengthMax != null && s.length() > lengthMax) {
|
||||
throw new ClientException("ValidationError", MessageFormat.format("{0} must be more than {1} characters", name, lengthMax));
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a string length. The string mustn't be empty.
|
||||
*
|
||||
* @param s String to validate
|
||||
* @param name Name of the parameter
|
||||
* @param lengthMin Minimum length (or null)
|
||||
* @param lengthMax Maximum length (or null)
|
||||
* @return String without white spaces
|
||||
* @throws ClientException
|
||||
*/
|
||||
public static String validateLength(String s, String name, Integer lengthMin, Integer lengthMax) throws JSONException {
|
||||
return validateLength(s, name, lengthMin, lengthMax, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the string is not null and is not only whitespaces.
|
||||
*
|
||||
* @param s String to validate
|
||||
* @param name Name of the parameter
|
||||
* @return String without white spaces
|
||||
* @throws JSONException
|
||||
*/
|
||||
public static String validateStringNotBlank(String s, String name) throws JSONException {
|
||||
return validateLength(s, name, 1, null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the string is an email.
|
||||
*
|
||||
* @param s String to validate
|
||||
* @param name Name of the parameter
|
||||
* @throws JSONException
|
||||
*/
|
||||
public static void validateEmail(String s, String name) throws JSONException {
|
||||
if (!EMAIL_PATTERN.matcher(s).matches()) {
|
||||
throw new ClientException("ValidationError", MessageFormat.format("{0} must be an email", name));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the provided string matches an URL with HTTP or HTTPS scheme.
|
||||
*
|
||||
* @param s String to validate
|
||||
* @param name Name of the parameter
|
||||
* @return Stripped URL
|
||||
* @throws JSONException
|
||||
*/
|
||||
public static String validateHttpUrl(String s, String name) throws JSONException {
|
||||
s = StringUtils.strip(s);
|
||||
if (!HTTP_URL_PATTERN.matcher(s).matches()) {
|
||||
throw new ClientException("ValidationError", MessageFormat.format("{0} must be an HTTP(s) URL", name));
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the string uses only alphanumerical or underscore characters.
|
||||
*
|
||||
* @param s String to validate
|
||||
* @param name Name of the parameter
|
||||
* @throws JSONException
|
||||
*/
|
||||
public static void validateAlphanumeric(String s, String name) throws JSONException {
|
||||
if (!ALPHANUMERIC_PATTERN.matcher(s).matches()) {
|
||||
throw new ClientException("ValidationError", MessageFormat.format("{0} must have only alphanumeric or underscore characters", name));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and parses a date.
|
||||
*
|
||||
* @param s String to validate
|
||||
* @param name Name of the parameter
|
||||
* @param nullable True if the string can be empty or null
|
||||
* @return Parsed date
|
||||
* @throws JSONException
|
||||
*/
|
||||
public static Date validateDate(String s, String name, boolean nullable) throws JSONException {
|
||||
if (Strings.isNullOrEmpty(s)) {
|
||||
if (!nullable) {
|
||||
throw new ClientException("ValidationError", MessageFormat.format("{0} must be set", name));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
try {
|
||||
return new DateTime(Long.parseLong(s)).toDate();
|
||||
} catch (NumberFormatException e) {
|
||||
throw new ClientException("ValidationError", MessageFormat.format("{0} must be a date", name));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a locale.
|
||||
*
|
||||
* @param localeId String to validate
|
||||
* @param name Name of the parameter
|
||||
* @return String without white spaces
|
||||
* @param nullable True if the string can be empty or null
|
||||
* @throws ClientException
|
||||
*/
|
||||
public static String validateLocale(String localeId, String name, boolean nullable) throws JSONException {
|
||||
localeId = StringUtils.strip(localeId);
|
||||
if (StringUtils.isEmpty(localeId)) {
|
||||
if (!nullable) {
|
||||
throw new ClientException("ValidationError", MessageFormat.format("{0} is required", name));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
LocaleDao localeDao = new LocaleDao();
|
||||
Locale locale = localeDao.getById(localeId);
|
||||
if (locale == null) {
|
||||
throw new ClientException("ValidationError", "Locale not found: " + localeId);
|
||||
}
|
||||
return localeId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a theme.
|
||||
*
|
||||
* @param themeId ID of the theme to validate
|
||||
* @param name Name of the parameter
|
||||
* @return String without white spaces
|
||||
* @param nullable True if the string can be empty or null
|
||||
* @throws ClientException
|
||||
*/
|
||||
public static String validateTheme(String themeId, String name, boolean nullable) throws JSONException {
|
||||
themeId = StringUtils.strip(themeId);
|
||||
if (StringUtils.isEmpty(themeId)) {
|
||||
if (!nullable) {
|
||||
throw new ClientException("ValidationError", MessageFormat.format("{0} is required", name));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
ThemeDao themeDao = new ThemeDao();
|
||||
List<String> themeList = themeDao.findAll();
|
||||
if (!themeList.contains(themeId)) {
|
||||
throw new ClientException("ValidationError", "Theme not found: " + themeId);
|
||||
}
|
||||
return themeId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.sismics.security;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.joda.time.DateTimeZone;
|
||||
|
||||
/**
|
||||
* Anonymous principal.
|
||||
*
|
||||
* @author jtremeaux
|
||||
*/
|
||||
public class AnonymousPrincipal implements IPrincipal {
|
||||
public static final String ANONYMOUS = "anonymous";
|
||||
|
||||
/**
|
||||
* User locale.
|
||||
*/
|
||||
private Locale locale;
|
||||
|
||||
/**
|
||||
* User timezone.
|
||||
*/
|
||||
private DateTimeZone dateTimeZone;
|
||||
|
||||
/**
|
||||
* Constructor of AnonymousPrincipal.
|
||||
*/
|
||||
public AnonymousPrincipal() {
|
||||
// NOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return ANONYMOUS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAnonymous() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locale getLocale() {
|
||||
return locale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter of locale.
|
||||
*
|
||||
* @param locale locale
|
||||
*/
|
||||
public void setLocale(Locale locale) {
|
||||
this.locale = locale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DateTimeZone getDateTimeZone() {
|
||||
return dateTimeZone;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEmail() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter of dateTimeZone.
|
||||
*
|
||||
* @param dateTimeZone dateTimeZone
|
||||
*/
|
||||
public void setDateTimeZone(DateTimeZone dateTimeZone) {
|
||||
this.dateTimeZone = dateTimeZone;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.sismics.security;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.joda.time.DateTimeZone;
|
||||
|
||||
/**
|
||||
* Interface of principals.
|
||||
*
|
||||
* @author jtremeaux
|
||||
*/
|
||||
public interface IPrincipal extends Principal {
|
||||
/**
|
||||
* Checks if the principal is anonymous.
|
||||
*
|
||||
* @return True if the principal is anonymous.
|
||||
*/
|
||||
boolean isAnonymous();
|
||||
|
||||
/**
|
||||
* Returns the ID of the connected user, or null if the user is anonymous
|
||||
*
|
||||
* @return ID of the connected user
|
||||
*/
|
||||
public String getId();
|
||||
|
||||
/**
|
||||
* Returns the locale of the principal.
|
||||
*
|
||||
* @return Locale of the principal
|
||||
*/
|
||||
public Locale getLocale();
|
||||
|
||||
/**
|
||||
* Returns the timezone of the principal.
|
||||
*
|
||||
* @return Timezone of the principal
|
||||
*/
|
||||
public DateTimeZone getDateTimeZone();
|
||||
|
||||
/**
|
||||
* Returns the email of the principal.
|
||||
*
|
||||
* @return Email of the principal
|
||||
*/
|
||||
public String getEmail();
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package com.sismics.security;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
import org.joda.time.DateTimeZone;
|
||||
|
||||
/**
|
||||
* Authenticated users principal.
|
||||
*
|
||||
* @author jtremeaux
|
||||
*/
|
||||
public class UserPrincipal implements IPrincipal {
|
||||
/**
|
||||
* ID of the user.
|
||||
*/
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* Username of the user.
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* Locale of the principal.
|
||||
*/
|
||||
private Locale locale;
|
||||
|
||||
/**
|
||||
* Timezone of the principal.
|
||||
*/
|
||||
private DateTimeZone dateTimeZone;
|
||||
|
||||
/**
|
||||
* Email of the principal.
|
||||
*/
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* User base functions.
|
||||
*/
|
||||
private Set<String> baseFunctionSet;
|
||||
|
||||
/**
|
||||
* Constructor of UserPrincipal.
|
||||
*
|
||||
* @param id ID of the user
|
||||
* @param name Usrename of the user
|
||||
*/
|
||||
public UserPrincipal(String id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAnonymous() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter of id.
|
||||
*
|
||||
* @param id id
|
||||
*/
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter of name.
|
||||
*
|
||||
* @param name name
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locale getLocale() {
|
||||
return locale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter of locale.
|
||||
*
|
||||
* @param locale locale
|
||||
*/
|
||||
public void setLocale(Locale locale) {
|
||||
this.locale = locale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DateTimeZone getDateTimeZone() {
|
||||
return dateTimeZone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter of dateTimeZone.
|
||||
*
|
||||
* @param dateTimeZone dateTimeZone
|
||||
*/
|
||||
public void setDateTimeZone(DateTimeZone dateTimeZone) {
|
||||
this.dateTimeZone = dateTimeZone;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter of email.
|
||||
*
|
||||
* @param email email
|
||||
*/
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter of baseFunctionSet.
|
||||
*
|
||||
* @return baseFunctionSet
|
||||
*/
|
||||
public Set<String> getBaseFunctionSet() {
|
||||
return baseFunctionSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter of baseFunctionSet.
|
||||
*
|
||||
* @param baseFunctionSet baseFunctionSet
|
||||
*/
|
||||
public void setBaseFunctionSet(Set<String> baseFunctionSet) {
|
||||
this.baseFunctionSet = baseFunctionSet;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
package com.sismics.util.filter;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.EntityTransaction;
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.FilterConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.log4j.Level;
|
||||
import org.apache.log4j.PatternLayout;
|
||||
import org.apache.log4j.RollingFileAppender;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.sismics.docs.core.constant.Constants;
|
||||
import com.sismics.docs.core.model.context.AppContext;
|
||||
import com.sismics.docs.core.util.DirectoryUtil;
|
||||
import com.sismics.docs.core.util.TransactionUtil;
|
||||
import com.sismics.util.EnvironmentUtil;
|
||||
import com.sismics.util.context.ThreadLocalContext;
|
||||
import com.sismics.util.jpa.EMF;
|
||||
|
||||
/**
|
||||
* Filter used to process a couple things in the request context.
|
||||
*
|
||||
* @author jtremeaux
|
||||
*/
|
||||
public class RequestContextFilter implements Filter {
|
||||
/**
|
||||
* Logger.
|
||||
*/
|
||||
private static final Logger log = LoggerFactory.getLogger(RequestContextFilter.class);
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
// Force the locale in order to not depend on the execution environment
|
||||
Locale.setDefault(new Locale(Constants.DEFAULT_LOCALE_ID));
|
||||
|
||||
// Injects the webapp root
|
||||
String webappRoot = filterConfig.getServletContext().getRealPath("/");
|
||||
EnvironmentUtil.setWebappRoot(webappRoot);
|
||||
|
||||
// Initialize the app directory
|
||||
File baseDataDirectory = null;
|
||||
try {
|
||||
baseDataDirectory = DirectoryUtil.getBaseDataDirectory();
|
||||
} catch (Exception e) {
|
||||
log.error("Error initializing base data directory", e);
|
||||
}
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info(MessageFormat.format("Using base data directory: {0}", baseDataDirectory.toString()));
|
||||
}
|
||||
|
||||
// Initialize file logger
|
||||
RollingFileAppender fileAppender = new RollingFileAppender();
|
||||
fileAppender.setName("FILE");
|
||||
fileAppender.setFile(DirectoryUtil.getLogDirectory() + File.separator + "docs.log");
|
||||
fileAppender.setLayout(new PatternLayout("%d{DATE} %p %l %m %n"));
|
||||
fileAppender.setThreshold(Level.INFO);
|
||||
fileAppender.setAppend(true);
|
||||
fileAppender.setMaxFileSize("5MB");
|
||||
fileAppender.setMaxBackupIndex(5);
|
||||
fileAppender.activateOptions();
|
||||
org.apache.log4j.Logger.getRootLogger().addAppender(fileAppender);
|
||||
|
||||
// Initialize the application context
|
||||
TransactionUtil.handle(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
AppContext.getInstance();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
// NOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
|
||||
EntityManager em = null;
|
||||
|
||||
try {
|
||||
em = EMF.get().createEntityManager();
|
||||
} catch (Exception e) {
|
||||
throw new ServletException("Cannot create entity manager", e);
|
||||
}
|
||||
ThreadLocalContext context = ThreadLocalContext.get();
|
||||
context.setEntityManager(em);
|
||||
EntityTransaction tx = em.getTransaction();
|
||||
tx.begin();
|
||||
|
||||
try {
|
||||
filterChain.doFilter(request, response);
|
||||
} catch (Exception e) {
|
||||
ThreadLocalContext.cleanup();
|
||||
|
||||
log.error("An exception occured, rolling back current transaction", e);
|
||||
|
||||
// If an unprocessed error comes up from the application layers (Jersey...), rollback the transaction (should not happen)
|
||||
if (em.isOpen()) {
|
||||
if (em.getTransaction() != null && em.getTransaction().isActive()) {
|
||||
em.getTransaction().rollback();
|
||||
}
|
||||
|
||||
try {
|
||||
em.close();
|
||||
} catch (Exception ce) {
|
||||
log.error("Error closing entity manager", ce);
|
||||
}
|
||||
}
|
||||
throw new ServletException(e);
|
||||
}
|
||||
|
||||
ThreadLocalContext.cleanup();
|
||||
|
||||
// No error processing the request : commit / rollback the current transaction depending on the HTTP code
|
||||
if (em.isOpen()) {
|
||||
if (em.getTransaction() != null && em.getTransaction().isActive()) {
|
||||
HttpServletResponse r = (HttpServletResponse) response;
|
||||
int statusClass = r.getStatus() / 100;
|
||||
if (statusClass == 2 || statusClass == 3) {
|
||||
try {
|
||||
em.getTransaction().commit();
|
||||
} catch (Exception e) {
|
||||
log.error("Error during commit", e);
|
||||
r.sendError(500);
|
||||
}
|
||||
} else {
|
||||
em.getTransaction().rollback();
|
||||
}
|
||||
|
||||
try {
|
||||
em.close();
|
||||
} catch (Exception e) {
|
||||
log.error("Error closing entity manager", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
package com.sismics.util.filter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.FilterConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.sismics.docs.core.constant.Constants;
|
||||
import com.sismics.docs.core.dao.jpa.AuthenticationTokenDao;
|
||||
import com.sismics.docs.core.dao.jpa.RoleBaseFunctionDao;
|
||||
import com.sismics.docs.core.dao.jpa.UserDao;
|
||||
import com.sismics.docs.core.model.jpa.AuthenticationToken;
|
||||
import com.sismics.docs.core.model.jpa.User;
|
||||
import com.sismics.security.AnonymousPrincipal;
|
||||
import com.sismics.security.UserPrincipal;
|
||||
import com.sismics.util.LocaleUtil;
|
||||
|
||||
/**
|
||||
* This filter is used to authenticate the user having an active session via an authentication token stored in database.
|
||||
* The filter extracts the authentication token stored in a cookie.
|
||||
* If the ocokie exists and the token is valid, the filter injects a UserPrincipal into a request attribute.
|
||||
* If not, the user is anonymous, and the filter injects a AnonymousPrincipal into the request attribute.
|
||||
*
|
||||
* @author jtremeaux
|
||||
*/
|
||||
public class TokenBasedSecurityFilter implements Filter {
|
||||
/**
|
||||
* Logger.
|
||||
*/
|
||||
private static final Logger log = LoggerFactory.getLogger(TokenBasedSecurityFilter.class);
|
||||
|
||||
/**
|
||||
* Name of the cookie used to store the authentication token.
|
||||
*/
|
||||
public static final String COOKIE_NAME = "auth_token";
|
||||
|
||||
/**
|
||||
* Name of the attribute containing the principal.
|
||||
*/
|
||||
public static final String PRINCIPAL_ATTRIBUTE = "principal";
|
||||
|
||||
/**
|
||||
* Lifetime of the authentication token in seconds, since login.
|
||||
*/
|
||||
public static final int TOKEN_LONG_LIFETIME = 3600 * 24 * 365 * 20;
|
||||
|
||||
/**
|
||||
* Lifetime of the authentication token in seconds, since last connection.
|
||||
*/
|
||||
public static final int TOKEN_SESSION_LIFETIME = 3600 * 24;
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
// NOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
// NOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest req, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
|
||||
// Get the value of the client authentication token
|
||||
HttpServletRequest request = (HttpServletRequest) req;
|
||||
String authToken = null;
|
||||
if (request.getCookies() != null) {
|
||||
for (Cookie cookie : request.getCookies()) {
|
||||
if (COOKIE_NAME.equals(cookie.getName())) {
|
||||
authToken = cookie.getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get the corresponding server token
|
||||
AuthenticationTokenDao authenticationTokenDao = new AuthenticationTokenDao();
|
||||
AuthenticationToken authenticationToken = null;
|
||||
if (authToken != null) {
|
||||
authenticationToken = authenticationTokenDao.get(authToken);
|
||||
}
|
||||
|
||||
if (authenticationToken == null) {
|
||||
injectAnonymousUser(request);
|
||||
} else {
|
||||
// Check if the token is still valid
|
||||
if (isTokenExpired(authenticationToken)) {
|
||||
try {
|
||||
injectAnonymousUser(request);
|
||||
|
||||
// Destroy the expired token
|
||||
authenticationTokenDao.delete(authToken);
|
||||
} catch (Exception e) {
|
||||
if (log.isErrorEnabled()) {
|
||||
log.error(MessageFormat.format("Error deleting authentication token {0} ", authToken), e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Check if the user is still valid
|
||||
UserDao userDao = new UserDao();
|
||||
User user = userDao.getById(authenticationToken.getUserId());
|
||||
if (user != null && user.getDeleteDate() == null) {
|
||||
injectAuthenticatedUser(request, user);
|
||||
|
||||
// Update the last connection date
|
||||
authenticationTokenDao.updateLastConnectionDate(authenticationToken.getId());
|
||||
} else {
|
||||
injectAnonymousUser(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the token is expired.
|
||||
*
|
||||
* @param authenticationToken Authentication token
|
||||
* @return Token expired
|
||||
*/
|
||||
private boolean isTokenExpired(AuthenticationToken authenticationToken) {
|
||||
final long now = new Date().getTime();
|
||||
final long creationDate = authenticationToken.getCreationDate().getTime();
|
||||
if (authenticationToken.isLongLasted()) {
|
||||
return now >= creationDate + ((long) TOKEN_LONG_LIFETIME) * 1000L;
|
||||
} else {
|
||||
long date = authenticationToken.getLastConnectionDate() != null ?
|
||||
authenticationToken.getLastConnectionDate().getTime() : creationDate;
|
||||
return now >= date + ((long) TOKEN_SESSION_LIFETIME) * 1000L;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject an authenticated user into the request attributes.
|
||||
*
|
||||
* @param request HTTP request
|
||||
* @param user User to inject
|
||||
*/
|
||||
private void injectAuthenticatedUser(HttpServletRequest request, User user) {
|
||||
UserPrincipal userPrincipal = new UserPrincipal(user.getId(), user.getUsername());
|
||||
|
||||
// Add locale
|
||||
Locale locale = LocaleUtil.getLocale(user.getLocaleId());
|
||||
userPrincipal.setLocale(locale);
|
||||
|
||||
// Add base functions
|
||||
RoleBaseFunctionDao userBaseFuction = new RoleBaseFunctionDao();
|
||||
Set<String> baseFunctionSet = userBaseFuction.findByRoleId(user.getRoleId());
|
||||
userPrincipal.setBaseFunctionSet(baseFunctionSet);
|
||||
|
||||
request.setAttribute(PRINCIPAL_ATTRIBUTE, userPrincipal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject an anonymous user into the request attributes.
|
||||
*
|
||||
* @param request HTTP request
|
||||
*/
|
||||
private void injectAnonymousUser(HttpServletRequest request) {
|
||||
AnonymousPrincipal anonymousPrincipal = new AnonymousPrincipal();
|
||||
anonymousPrincipal.setLocale(request.getLocale());
|
||||
anonymousPrincipal.setDateTimeZone(DateTimeZone.forID(Constants.DEFAULT_TIMEZONE_ID));
|
||||
|
||||
request.setAttribute(PRINCIPAL_ATTRIBUTE, anonymousPrincipal);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package com.sismics.docs.rest;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.List;
|
||||
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
import javax.mail.internet.MimeUtility;
|
||||
|
||||
import org.glassfish.grizzly.http.server.HttpServer;
|
||||
import org.glassfish.grizzly.http.server.StaticHttpHandler;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.subethamail.wiser.Wiser;
|
||||
import org.subethamail.wiser.WiserMessage;
|
||||
|
||||
import com.sismics.docs.rest.descriptor.JerseyTestWebAppDescriptorFactory;
|
||||
import com.sismics.docs.rest.util.ClientUtil;
|
||||
import com.sun.jersey.test.framework.JerseyTest;
|
||||
|
||||
/**
|
||||
* Base class of integration tests with Jersey.
|
||||
*
|
||||
* @author jtremeaux
|
||||
*/
|
||||
public abstract class BaseJerseyTest extends JerseyTest {
|
||||
/**
|
||||
* Test email server.
|
||||
*/
|
||||
protected Wiser wiser;
|
||||
|
||||
/**
|
||||
* Test HTTP server.
|
||||
*/
|
||||
HttpServer httpServer;
|
||||
|
||||
/**
|
||||
* Utility class for the REST client.
|
||||
*/
|
||||
protected ClientUtil clientUtil;
|
||||
|
||||
/**
|
||||
* Constructor of BaseJerseyTest.
|
||||
*/
|
||||
public BaseJerseyTest() {
|
||||
super(JerseyTestWebAppDescriptorFactory.build());
|
||||
this.clientUtil = new ClientUtil(resource());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
wiser = new Wiser();
|
||||
wiser.setPort(2500);
|
||||
wiser.start();
|
||||
|
||||
String httpRoot = URLDecoder.decode(new File(getClass().getResource("/").getFile()).getAbsolutePath(), "utf-8");
|
||||
httpServer = HttpServer.createSimpleServer(httpRoot, "localhost", 9997);
|
||||
// Disable file cache to fix https://java.net/jira/browse/GRIZZLY-1350
|
||||
((StaticHttpHandler) httpServer.getServerConfiguration().getHttpHandlers().keySet().iterator().next()).setFileCacheEnabled(false);
|
||||
httpServer.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
super.tearDown();
|
||||
wiser.stop();
|
||||
httpServer.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts an email from the queue and consumes the email.
|
||||
*
|
||||
* @return Text of the email
|
||||
* @throws MessagingException
|
||||
* @throws IOException
|
||||
*/
|
||||
protected String popEmail() throws MessagingException, IOException {
|
||||
List<WiserMessage> wiserMessageList = wiser.getMessages();
|
||||
if (wiserMessageList.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
WiserMessage wiserMessage = wiserMessageList.get(wiserMessageList.size() - 1);
|
||||
wiserMessageList.remove(wiserMessageList.size() - 1);
|
||||
MimeMessage message = wiserMessage.getMimeMessage();
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
message.writeTo(os);
|
||||
String body = os.toString();
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a string to "quoted-printable" characters to compare with the contents of an email.
|
||||
*
|
||||
* @param input String to encode
|
||||
* @return Encoded string
|
||||
* @throws MessagingException
|
||||
* @throws IOException
|
||||
*/
|
||||
protected String encodeQuotedPrintable(String input) throws MessagingException, IOException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
OutputStream os = MimeUtility.encode(baos, "quoted-printable");
|
||||
os.write(input.getBytes());
|
||||
os.close();
|
||||
return baos.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.sismics.docs.rest.descriptor;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import com.sismics.util.filter.RequestContextFilter;
|
||||
import com.sismics.util.filter.TokenBasedSecurityFilter;
|
||||
import com.sun.jersey.test.framework.WebAppDescriptor;
|
||||
|
||||
/**
|
||||
* Jersey tests Webapp descriptor.
|
||||
*
|
||||
* @author jtremeaux
|
||||
*/
|
||||
public class JerseyTestWebAppDescriptorFactory {
|
||||
private static String basePath = new File("src/main/webapp").getAbsolutePath();
|
||||
|
||||
/**
|
||||
* Constructs a new descriptor.
|
||||
*
|
||||
* @return Descriptor
|
||||
*/
|
||||
public static WebAppDescriptor build() {
|
||||
// Target the base path to the Webapp resources
|
||||
System.setProperty("user.dir", basePath);
|
||||
System.setProperty("test", "true");
|
||||
|
||||
return new WebAppDescriptor.Builder("com.sismics.docs.rest.resource")
|
||||
.contextPath("docs")
|
||||
.addFilter(RequestContextFilter.class, "requestContextFilter")
|
||||
.addFilter(TokenBasedSecurityFilter.class, "tokenBasedSecurityFilter")
|
||||
.initParam("com.sun.jersey.spi.container.ContainerRequestFilters", "com.sun.jersey.api.container.filter.LoggingFilter")
|
||||
.initParam("com.sun.jersey.spi.container.ContainerResponseFilters", "com.sun.jersey.api.container.filter.LoggingFilter")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.sismics.docs.rest.filter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.ws.rs.core.Cookie;
|
||||
|
||||
import com.sismics.util.filter.TokenBasedSecurityFilter;
|
||||
import com.sun.jersey.api.client.ClientHandlerException;
|
||||
import com.sun.jersey.api.client.ClientRequest;
|
||||
import com.sun.jersey.api.client.ClientResponse;
|
||||
import com.sun.jersey.api.client.filter.ClientFilter;
|
||||
|
||||
/**
|
||||
* Filter to add the authentication token into a cookie.
|
||||
*
|
||||
* @author jtremeaux
|
||||
*/
|
||||
public class CookieAuthenticationFilter extends ClientFilter {
|
||||
private String authToken;
|
||||
|
||||
public CookieAuthenticationFilter(String authToken) {
|
||||
this.authToken = authToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientResponse handle(ClientRequest request) throws ClientHandlerException {
|
||||
Cookie cookie = new Cookie(TokenBasedSecurityFilter.COOKIE_NAME, authToken);
|
||||
List<Object> cookieList = new ArrayList<Object>();
|
||||
cookieList.add(cookie);
|
||||
if (authToken != null) {
|
||||
request.getHeaders().put("Cookie", cookieList);
|
||||
}
|
||||
ClientResponse response = getNext().handle(request);
|
||||
if (response.getCookies() != null) {
|
||||
cookieList.addAll(response.getCookies());
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package com.sismics.docs.rest.util;
|
||||
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.NewCookie;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import com.sismics.docs.rest.filter.CookieAuthenticationFilter;
|
||||
import com.sismics.util.filter.TokenBasedSecurityFilter;
|
||||
import com.sun.jersey.api.client.ClientResponse;
|
||||
import com.sun.jersey.api.client.ClientResponse.Status;
|
||||
import com.sun.jersey.api.client.WebResource;
|
||||
import com.sun.jersey.core.util.MultivaluedMapImpl;
|
||||
|
||||
/**
|
||||
* REST client utilities.
|
||||
*
|
||||
* @author jtremeaux
|
||||
*/
|
||||
public class ClientUtil {
|
||||
private WebResource resource;
|
||||
|
||||
/**
|
||||
* Constructor of ClientUtil.
|
||||
*
|
||||
* @param webResource Resource corresponding to the base URI of REST resources.
|
||||
*/
|
||||
public ClientUtil(WebResource resource) {
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a user.
|
||||
*
|
||||
* @param username Username
|
||||
*/
|
||||
public void createUser(String username) {
|
||||
// Login admin to create the user
|
||||
String adminAuthenticationToken = login("admin", "admin", false);
|
||||
|
||||
// Create the user
|
||||
WebResource userResource = resource.path("/user");
|
||||
userResource.addFilter(new CookieAuthenticationFilter(adminAuthenticationToken));
|
||||
MultivaluedMap<String, String> postParams = new MultivaluedMapImpl();
|
||||
postParams.putSingle("username", username);
|
||||
postParams.putSingle("email", username + "@docs.com");
|
||||
postParams.putSingle("password", "12345678");
|
||||
postParams.putSingle("time_zone", "Asia/Tokyo");
|
||||
ClientResponse response = userResource.put(ClientResponse.class, postParams);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
|
||||
// Logout admin
|
||||
logout(adminAuthenticationToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects a user to the application.
|
||||
*
|
||||
* @param username Username
|
||||
* @param password Password
|
||||
* @param remember Remember user
|
||||
* @return Authentication token
|
||||
*/
|
||||
public String login(String username, String password, Boolean remember) {
|
||||
WebResource userResource = resource.path("/user/login");
|
||||
MultivaluedMap<String, String> postParams = new MultivaluedMapImpl();
|
||||
postParams.putSingle("username", username);
|
||||
postParams.putSingle("password", password);
|
||||
postParams.putSingle("remember", remember.toString());
|
||||
ClientResponse response = userResource.post(ClientResponse.class, postParams);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
|
||||
return getAuthenticationCookie(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects a user to the application.
|
||||
*
|
||||
* @param username Username
|
||||
* @return Authentication token
|
||||
*/
|
||||
public String login(String username) {
|
||||
return login(username, "12345678", false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects a user from the application.
|
||||
*
|
||||
* @param authenticationToken Authentication token
|
||||
*/
|
||||
public void logout(String authenticationToken) {
|
||||
WebResource userResource = resource.path("/user/logout");
|
||||
userResource.addFilter(new CookieAuthenticationFilter(authenticationToken));
|
||||
ClientResponse response = userResource.post(ClientResponse.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the authentication token of the response.
|
||||
*
|
||||
* @param response Response
|
||||
* @return Authentication token
|
||||
*/
|
||||
public String getAuthenticationCookie(ClientResponse response) {
|
||||
String authToken = null;
|
||||
for (NewCookie cookie : response.getCookies()) {
|
||||
if (TokenBasedSecurityFilter.COOKIE_NAME.equals(cookie.getName())) {
|
||||
authToken = cookie.getValue();
|
||||
}
|
||||
}
|
||||
return authToken;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.sismics.docs.rest.util;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sismics.rest.exception.ClientException;
|
||||
import com.sismics.rest.util.ValidationUtil;
|
||||
|
||||
/**
|
||||
* Test the validations.
|
||||
*
|
||||
* @author jtremeaux
|
||||
*/
|
||||
public class TestValidationUtil {
|
||||
@Test
|
||||
public void testValidateHttpUrlFail() throws Exception {
|
||||
ValidationUtil.validateHttpUrl("http://www.google.com", "url");
|
||||
ValidationUtil.validateHttpUrl("https://www.google.com", "url");
|
||||
ValidationUtil.validateHttpUrl(" https://www.google.com ", "url");
|
||||
try {
|
||||
ValidationUtil.validateHttpUrl("ftp://www.google.com", "url");
|
||||
Assert.fail();
|
||||
} catch (ClientException e) {
|
||||
// NOP
|
||||
}
|
||||
try {
|
||||
ValidationUtil.validateHttpUrl("http://", "url");
|
||||
Assert.fail();
|
||||
} catch (ClientException e) {
|
||||
// NOP
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user