extensible authentication system

This commit is contained in:
Benjamin Gamard 2018-03-26 22:07:26 +02:00
parent c9606f98d3
commit 99d44f2a92
10 changed files with 132 additions and 27 deletions

View File

@ -38,11 +38,14 @@ public class EncryptionUtil {
* Generate a private key. * Generate a private key.
* *
* @return New random private key * @return New random private key
* @throws NoSuchAlgorithmException
*/ */
public static String generatePrivateKey() throws NoSuchAlgorithmException { public static String generatePrivateKey() {
SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); try {
return new BigInteger(176, random).toString(32); SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
return new BigInteger(176, random).toString(32);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
} }
/** /**

View File

@ -50,6 +50,12 @@ public class RoutingUtil {
} }
} }
/**
* Send an email when a route step is validated.
*
* @param documentId Document ID
* @param routeStepDto Route step DTO
*/
public static void sendRouteStepEmail(String documentId, RouteStepDto routeStepDto) { public static void sendRouteStepEmail(String documentId, RouteStepDto routeStepDto) {
DocumentDao documentDao = new DocumentDao(); DocumentDao documentDao = new DocumentDao();
Document document = documentDao.getById(documentId); Document document = documentDao.getById(documentId);

View File

@ -0,0 +1,19 @@
package com.sismics.docs.core.util.authentication;
import com.sismics.docs.core.model.jpa.User;
/**
* An authentication handler.
*
* @author bgamard
*/
public interface AuthenticationHandler {
/**
* Authenticate a user.
*
* @param username Username
* @param password Password
* @return Authenticated user
*/
User authenticate(String username, String password);
}

View File

@ -0,0 +1,45 @@
package com.sismics.docs.core.util.authentication;
import com.google.common.collect.Lists;
import com.sismics.docs.core.model.jpa.User;
import com.sismics.util.ClasspathScanner;
import java.util.List;
import java.util.stream.Collectors;
/**
* User utilities.
*/
public class AuthenticationUtil {
/**
* List of authentication handlers scanned in the classpath.
*/
private static final List<AuthenticationHandler> AUTH_HANDLERS = Lists.newArrayList(
new ClasspathScanner<AuthenticationHandler>().findClasses(AuthenticationHandler.class, "com.sismics.docs.core.util.authentication")
.stream()
.map(clazz -> {
try {
return clazz.newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
}).collect(Collectors.toList()));
/**
* Authenticate a user.
*
* @param username Username
* @param password Password
* @return Authenticated user
*/
public static User authenticate(String username, String password) {
for (AuthenticationHandler authenticationHandler : AUTH_HANDLERS) {
User user = authenticationHandler.authenticate(username, password);
if (user != null) {
return user;
}
}
return null;
}
}

View File

@ -0,0 +1,19 @@
package com.sismics.docs.core.util.authentication;
import com.sismics.docs.core.dao.jpa.UserDao;
import com.sismics.docs.core.model.jpa.User;
import com.sismics.util.ClasspathScanner;
/**
* Authenticate using the internal database.
*
* @author bgamard
*/
@ClasspathScanner.Priority(100) // We can add handlers before this one
public class InternalAuthenticationHandler implements AuthenticationHandler {
@Override
public User authenticate(String username, String password) {
UserDao userDao = new UserDao();
return userDao.authenticate(username, password);
}
}

View File

@ -1,13 +1,13 @@
package com.sismics.util; package com.sismics.util;
import com.google.common.collect.Sets; import com.google.common.collect.Lists;
import com.google.common.reflect.ClassPath; import com.google.common.reflect.ClassPath;
import com.sismics.docs.core.util.format.PdfFormatHandler; import com.sismics.docs.core.util.format.PdfFormatHandler;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.Set; import java.util.List;
/** /**
* Classes scanner. * Classes scanner.
@ -23,11 +23,11 @@ public class ClasspathScanner<T> {
* *
* @param topClass Top class or interface * @param topClass Top class or interface
* @param pkg In this package * @param pkg In this package
* @return Set of classes * @return List of classes
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public Set<Class<T>> findClasses(Class<T> topClass, String pkg) { public List<Class<T>> findClasses(Class<T> topClass, String pkg) {
Set<Class<T>> classes = Sets.newHashSet(); List<Class<T>> classes = Lists.newArrayList();
try { try {
for (ClassPath.ClassInfo classInfo : ClassPath.from(topClass.getClassLoader()).getTopLevelClasses(pkg)) { for (ClassPath.ClassInfo classInfo : ClassPath.from(topClass.getClassLoader()).getTopLevelClasses(pkg)) {
Class<?> clazz = classInfo.load(); Class<?> clazz = classInfo.load();
@ -38,7 +38,22 @@ public class ClasspathScanner<T> {
} catch (Exception e) { } catch (Exception e) {
log.error("Error loading format handlers", e); log.error("Error loading format handlers", e);
} }
classes.sort((o1, o2) -> {
Priority priority1 = o1.getDeclaredAnnotation(Priority.class);
Priority priority2 = o2.getDeclaredAnnotation(Priority.class);
return Integer.compare(priority1 == null ? Integer.MAX_VALUE : priority1.value(),
priority2 == null ? Integer.MAX_VALUE : priority2.value());
});
log.info("Found " + classes.size() + " classes for " + topClass.getSimpleName()); log.info("Found " + classes.size() + " classes for " + topClass.getSimpleName());
return classes; return classes;
} }
/**
* Classpath scanning priority.
*/
public @interface Priority {
int value() default Integer.MAX_VALUE;
}
} }

View File

@ -3,6 +3,7 @@ package com.sismics.docs.core.dao.jpa;
import com.sismics.docs.BaseTransactionalTest; import com.sismics.docs.BaseTransactionalTest;
import com.sismics.docs.core.model.jpa.User; import com.sismics.docs.core.model.jpa.User;
import com.sismics.docs.core.util.TransactionUtil; import com.sismics.docs.core.util.TransactionUtil;
import com.sismics.docs.core.util.authentication.InternalAuthenticationHandler;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
@ -18,6 +19,7 @@ public class TestJpa extends BaseTransactionalTest {
UserDao userDao = new UserDao(); UserDao userDao = new UserDao();
User user = new User(); User user = new User();
user.setUsername("username"); user.setUsername("username");
user.setPassword("12345678");
user.setEmail("toto@docs.com"); user.setEmail("toto@docs.com");
user.setRoleId("admin"); user.setRoleId("admin");
user.setStorageCurrent(0l); user.setStorageCurrent(0l);
@ -31,5 +33,8 @@ public class TestJpa extends BaseTransactionalTest {
user = userDao.getById(id); user = userDao.getById(id);
Assert.assertNotNull(user); Assert.assertNotNull(user);
Assert.assertEquals("toto@docs.com", user.getEmail()); Assert.assertEquals("toto@docs.com", user.getEmail());
// Authenticate using the database
Assert.assertNotNull(new InternalAuthenticationHandler().authenticate("username", "12345678"));
} }
} }

View File

@ -1,15 +1,13 @@
package com.sismics.docs.core.util; package com.sismics.docs.core.util;
import java.io.InputStream;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import org.junit.Assert;
import org.junit.Test;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.io.ByteStreams; import com.google.common.io.ByteStreams;
import org.junit.Assert;
import org.junit.Test;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import java.io.InputStream;
/** /**
* Test of the encryption utilities. * Test of the encryption utilities.
@ -18,7 +16,7 @@ import com.google.common.io.ByteStreams;
*/ */
public class TestEncryptUtil { public class TestEncryptUtil {
@Test @Test
public void generatePrivateKeyTest() throws Exception { public void generatePrivateKeyTest() {
String key = EncryptionUtil.generatePrivateKey(); String key = EncryptionUtil.generatePrivateKey();
System.out.println(key); System.out.println(key);
Assert.assertFalse(Strings.isNullOrEmpty(key)); Assert.assertFalse(Strings.isNullOrEmpty(key));

View File

@ -5,7 +5,6 @@ import com.sismics.docs.core.dao.jpa.UserDao;
import com.sismics.docs.core.model.jpa.User; import com.sismics.docs.core.model.jpa.User;
import javax.servlet.FilterConfig; import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
/** /**
@ -26,7 +25,7 @@ public class HeaderBasedSecurityFilter extends SecurityFilter {
private boolean enabled; private boolean enabled;
@Override @Override
public void init(FilterConfig filterConfig) throws ServletException { public void init(FilterConfig filterConfig) {
enabled = Boolean.parseBoolean(filterConfig.getInitParameter("enabled")) enabled = Boolean.parseBoolean(filterConfig.getInitParameter("enabled"))
|| Boolean.parseBoolean(System.getProperty("docs.header_authentication")); || Boolean.parseBoolean(System.getProperty("docs.header_authentication"));
} }

View File

@ -16,6 +16,7 @@ import com.sismics.docs.core.model.context.AppContext;
import com.sismics.docs.core.model.jpa.*; import com.sismics.docs.core.model.jpa.*;
import com.sismics.docs.core.util.ConfigUtil; import com.sismics.docs.core.util.ConfigUtil;
import com.sismics.docs.core.util.EncryptionUtil; import com.sismics.docs.core.util.EncryptionUtil;
import com.sismics.docs.core.util.authentication.AuthenticationUtil;
import com.sismics.docs.core.util.jpa.SortCriteria; import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.docs.rest.constant.BaseFunction; import com.sismics.docs.rest.constant.BaseFunction;
import com.sismics.rest.exception.ClientException; import com.sismics.rest.exception.ClientException;
@ -38,7 +39,6 @@ import javax.ws.rs.*;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.NewCookie; import javax.ws.rs.core.NewCookie;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import java.security.NoSuchAlgorithmException;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -101,11 +101,7 @@ public class UserResource extends BaseResource {
user.setEmail(email); user.setEmail(email);
user.setStorageQuota(storageQuota); user.setStorageQuota(storageQuota);
user.setStorageCurrent(0L); user.setStorageCurrent(0L);
try { user.setPrivateKey(EncryptionUtil.generatePrivateKey());
user.setPrivateKey(EncryptionUtil.generatePrivateKey());
} catch (NoSuchAlgorithmException e) {
throw new ServerException("PrivateKeyError", "Error while generating a private key", e);
}
user.setCreateDate(new Date()); user.setCreateDate(new Date());
// Create the user // Create the user
@ -339,7 +335,7 @@ public class UserResource extends BaseResource {
} }
} else { } else {
// Login as a normal user // Login as a normal user
user = userDao.authenticate(username, password); user = AuthenticationUtil.authenticate(username, password);
} }
if (user == null) { if (user == null) {
throw new ForbiddenClientException(); throw new ForbiddenClientException();