From 99d44f2a92886e8c717a81913de02ebc334fe9c1 Mon Sep 17 00:00:00 2001 From: Benjamin Gamard Date: Mon, 26 Mar 2018 22:07:26 +0200 Subject: [PATCH] extensible authentication system --- .../docs/core/util/EncryptionUtil.java | 11 +++-- .../sismics/docs/core/util/RoutingUtil.java | 6 +++ .../authentication/AuthenticationHandler.java | 19 ++++++++ .../authentication/AuthenticationUtil.java | 45 +++++++++++++++++++ .../InternalAuthenticationHandler.java | 19 ++++++++ .../com/sismics/util/ClasspathScanner.java | 25 ++++++++--- .../sismics/docs/core/dao/jpa/TestJpa.java | 5 +++ .../docs/core/util/TestEncryptUtil.java | 16 +++---- .../filter/HeaderBasedSecurityFilter.java | 3 +- .../docs/rest/resource/UserResource.java | 10 ++--- 10 files changed, 132 insertions(+), 27 deletions(-) create mode 100644 docs-core/src/main/java/com/sismics/docs/core/util/authentication/AuthenticationHandler.java create mode 100644 docs-core/src/main/java/com/sismics/docs/core/util/authentication/AuthenticationUtil.java create mode 100644 docs-core/src/main/java/com/sismics/docs/core/util/authentication/InternalAuthenticationHandler.java diff --git a/docs-core/src/main/java/com/sismics/docs/core/util/EncryptionUtil.java b/docs-core/src/main/java/com/sismics/docs/core/util/EncryptionUtil.java index 765124d6..0713280f 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/util/EncryptionUtil.java +++ b/docs-core/src/main/java/com/sismics/docs/core/util/EncryptionUtil.java @@ -38,11 +38,14 @@ public class EncryptionUtil { * Generate a private key. * * @return New random private key - * @throws NoSuchAlgorithmException */ - public static String generatePrivateKey() throws NoSuchAlgorithmException { - SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); - return new BigInteger(176, random).toString(32); + public static String generatePrivateKey() { + try { + SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); + return new BigInteger(176, random).toString(32); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } } /** diff --git a/docs-core/src/main/java/com/sismics/docs/core/util/RoutingUtil.java b/docs-core/src/main/java/com/sismics/docs/core/util/RoutingUtil.java index 9e6a7fe7..fba6af86 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/util/RoutingUtil.java +++ b/docs-core/src/main/java/com/sismics/docs/core/util/RoutingUtil.java @@ -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) { DocumentDao documentDao = new DocumentDao(); Document document = documentDao.getById(documentId); diff --git a/docs-core/src/main/java/com/sismics/docs/core/util/authentication/AuthenticationHandler.java b/docs-core/src/main/java/com/sismics/docs/core/util/authentication/AuthenticationHandler.java new file mode 100644 index 00000000..4998d614 --- /dev/null +++ b/docs-core/src/main/java/com/sismics/docs/core/util/authentication/AuthenticationHandler.java @@ -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); +} diff --git a/docs-core/src/main/java/com/sismics/docs/core/util/authentication/AuthenticationUtil.java b/docs-core/src/main/java/com/sismics/docs/core/util/authentication/AuthenticationUtil.java new file mode 100644 index 00000000..bf023d5b --- /dev/null +++ b/docs-core/src/main/java/com/sismics/docs/core/util/authentication/AuthenticationUtil.java @@ -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 AUTH_HANDLERS = Lists.newArrayList( + new ClasspathScanner().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; + } +} diff --git a/docs-core/src/main/java/com/sismics/docs/core/util/authentication/InternalAuthenticationHandler.java b/docs-core/src/main/java/com/sismics/docs/core/util/authentication/InternalAuthenticationHandler.java new file mode 100644 index 00000000..4441712f --- /dev/null +++ b/docs-core/src/main/java/com/sismics/docs/core/util/authentication/InternalAuthenticationHandler.java @@ -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); + } +} diff --git a/docs-core/src/main/java/com/sismics/util/ClasspathScanner.java b/docs-core/src/main/java/com/sismics/util/ClasspathScanner.java index b67ec262..53c06719 100644 --- a/docs-core/src/main/java/com/sismics/util/ClasspathScanner.java +++ b/docs-core/src/main/java/com/sismics/util/ClasspathScanner.java @@ -1,13 +1,13 @@ package com.sismics.util; -import com.google.common.collect.Sets; +import com.google.common.collect.Lists; import com.google.common.reflect.ClassPath; import com.sismics.docs.core.util.format.PdfFormatHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.Modifier; -import java.util.Set; +import java.util.List; /** * Classes scanner. @@ -23,11 +23,11 @@ public class ClasspathScanner { * * @param topClass Top class or interface * @param pkg In this package - * @return Set of classes + * @return List of classes */ @SuppressWarnings("unchecked") - public Set> findClasses(Class topClass, String pkg) { - Set> classes = Sets.newHashSet(); + public List> findClasses(Class topClass, String pkg) { + List> classes = Lists.newArrayList(); try { for (ClassPath.ClassInfo classInfo : ClassPath.from(topClass.getClassLoader()).getTopLevelClasses(pkg)) { Class clazz = classInfo.load(); @@ -38,7 +38,22 @@ public class ClasspathScanner { } catch (Exception 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()); return classes; } + + /** + * Classpath scanning priority. + */ + public @interface Priority { + int value() default Integer.MAX_VALUE; + } } diff --git a/docs-core/src/test/java/com/sismics/docs/core/dao/jpa/TestJpa.java b/docs-core/src/test/java/com/sismics/docs/core/dao/jpa/TestJpa.java index f53a2847..d2adb1d7 100644 --- a/docs-core/src/test/java/com/sismics/docs/core/dao/jpa/TestJpa.java +++ b/docs-core/src/test/java/com/sismics/docs/core/dao/jpa/TestJpa.java @@ -3,6 +3,7 @@ package com.sismics.docs.core.dao.jpa; import com.sismics.docs.BaseTransactionalTest; import com.sismics.docs.core.model.jpa.User; import com.sismics.docs.core.util.TransactionUtil; +import com.sismics.docs.core.util.authentication.InternalAuthenticationHandler; import org.junit.Assert; import org.junit.Test; @@ -18,6 +19,7 @@ public class TestJpa extends BaseTransactionalTest { UserDao userDao = new UserDao(); User user = new User(); user.setUsername("username"); + user.setPassword("12345678"); user.setEmail("toto@docs.com"); user.setRoleId("admin"); user.setStorageCurrent(0l); @@ -31,5 +33,8 @@ public class TestJpa extends BaseTransactionalTest { user = userDao.getById(id); Assert.assertNotNull(user); Assert.assertEquals("toto@docs.com", user.getEmail()); + + // Authenticate using the database + Assert.assertNotNull(new InternalAuthenticationHandler().authenticate("username", "12345678")); } } diff --git a/docs-core/src/test/java/com/sismics/docs/core/util/TestEncryptUtil.java b/docs-core/src/test/java/com/sismics/docs/core/util/TestEncryptUtil.java index 68dbbe8b..197626df 100644 --- a/docs-core/src/test/java/com/sismics/docs/core/util/TestEncryptUtil.java +++ b/docs-core/src/test/java/com/sismics/docs/core/util/TestEncryptUtil.java @@ -1,15 +1,13 @@ 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.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. @@ -18,7 +16,7 @@ import com.google.common.io.ByteStreams; */ public class TestEncryptUtil { @Test - public void generatePrivateKeyTest() throws Exception { + public void generatePrivateKeyTest() { String key = EncryptionUtil.generatePrivateKey(); System.out.println(key); Assert.assertFalse(Strings.isNullOrEmpty(key)); diff --git a/docs-web-common/src/main/java/com/sismics/util/filter/HeaderBasedSecurityFilter.java b/docs-web-common/src/main/java/com/sismics/util/filter/HeaderBasedSecurityFilter.java index 33733f25..0d20dd32 100644 --- a/docs-web-common/src/main/java/com/sismics/util/filter/HeaderBasedSecurityFilter.java +++ b/docs-web-common/src/main/java/com/sismics/util/filter/HeaderBasedSecurityFilter.java @@ -5,7 +5,6 @@ import com.sismics.docs.core.dao.jpa.UserDao; import com.sismics.docs.core.model.jpa.User; import javax.servlet.FilterConfig; -import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; /** @@ -26,7 +25,7 @@ public class HeaderBasedSecurityFilter extends SecurityFilter { private boolean enabled; @Override - public void init(FilterConfig filterConfig) throws ServletException { + public void init(FilterConfig filterConfig) { enabled = Boolean.parseBoolean(filterConfig.getInitParameter("enabled")) || Boolean.parseBoolean(System.getProperty("docs.header_authentication")); } diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/UserResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/UserResource.java index a62c4eb5..5ff76dfe 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/UserResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/UserResource.java @@ -16,6 +16,7 @@ import com.sismics.docs.core.model.context.AppContext; import com.sismics.docs.core.model.jpa.*; import com.sismics.docs.core.util.ConfigUtil; 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.rest.constant.BaseFunction; 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.NewCookie; import javax.ws.rs.core.Response; -import java.security.NoSuchAlgorithmException; import java.util.Date; import java.util.List; import java.util.Set; @@ -101,11 +101,7 @@ public class UserResource extends BaseResource { user.setEmail(email); user.setStorageQuota(storageQuota); user.setStorageCurrent(0L); - try { - user.setPrivateKey(EncryptionUtil.generatePrivateKey()); - } catch (NoSuchAlgorithmException e) { - throw new ServerException("PrivateKeyError", "Error while generating a private key", e); - } + user.setPrivateKey(EncryptionUtil.generatePrivateKey()); user.setCreateDate(new Date()); // Create the user @@ -339,7 +335,7 @@ public class UserResource extends BaseResource { } } else { // Login as a normal user - user = userDao.authenticate(username, password); + user = AuthenticationUtil.authenticate(username, password); } if (user == null) { throw new ForbiddenClientException();