Skip to content
Snippets Groups Projects
Commit fa4245b3 authored by Felix Sidokhine's avatar Felix Sidokhine
Browse files

introduced tokens instead of whatever we had before which returns the token and the expiry

parent 0fc217b9
No related branches found
No related tags found
No related merge requests found
Showing
with 346 additions and 42 deletions
<?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/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>jams3-parent</artifactId>
<groupId>net.jami</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>authentication-module</artifactId>
<dependencies>
<dependency>
<groupId>net.jami</groupId>
<artifactId>jams-common</artifactId>
<version>${revision}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.jami</groupId>
<artifactId>datastore</artifactId>
<version>${revision}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.jami</groupId>
<artifactId>jami-dht</artifactId>
<version>${revision}</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>${maven.shade.version}</version>
<executions>
<!-- Run shade goal on package phase -->
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<outputFile>../jams/libs/${project.artifactId}.jar</outputFile>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
<transformers>
<!-- add Main-Class to manifest file -->
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Main-Class></Main-Class>
<Implementation-Title>${project.artifactId}</Implementation-Title>
<Implementation-Version>${project.version}</Implementation-Version>
<Class-Path>.</Class-Path>
</manifestEntries>
</transformer>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
\ No newline at end of file
package net.jami.jams.server.core.workflows;
package net.jami.jams.authmodule;
import net.jami.jams.common.objects.user.User;
import net.jami.jams.dht.DeviceReceiptGenerator;
import net.jami.jams.dht.ETHAddressGenerator;
import static net.jami.jams.server.Server.certificateAuthority;
import static net.jami.jams.authmodule.UserAuthenticationModule.certificateAuthority;
import static net.jami.jams.authmodule.UserAuthenticationModule.datastore;
public class RegisterUserFlow {
//Get the CA, sign, return the Jami ID.
public static User createUser(User user){
public static void createUser(User user){
//This generates the X509 Fields we need.
user = certificateAuthority.getSignedCertificate(user);
String[] ethKeyPair = null;
......@@ -19,6 +20,6 @@ public class RegisterUserFlow {
user.setEthAddress(ethKeyPair[0]);
user.setEthKey(ethKeyPair[1]);
user.setJamiId(DeviceReceiptGenerator.generateJamiId(user));
return user;
datastore.getUserDao().storeObject(user);
}
}
package net.jami.jams.authmodule;
import net.jami.jams.common.authmodule.TokenHolder;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class TokenController extends Timer {
private ConcurrentHashMap<String, TokenHolder> tokenMap = new ConcurrentHashMap<>();
public TokenController() {
this.schedule(new TimerTask() {
@Override
public void run() {
tokenMap.forEach( (k,v) -> {
if(v.getExpires() < System.currentTimeMillis()) tokenMap.remove(k);
});
}
},0,1000);
}
public String generateToken(String username, Long expiry){
String token = UUID.randomUUID().toString();
tokenMap.put(username,new TokenHolder(token,expiry));
return token;
}
public String getUsernameFromToken(String token){
StringBuilder username = new StringBuilder();
tokenMap.forEach( (k,v) -> {
if(v.getToken().equals(token)){
username.append(v.getToken());
}
});
if(username.length() != 0) return username.toString();
return null;
}
}
package net.jami.jams.server.core.usermanagement;
package net.jami.jams.authmodule;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.jami.datastore.main.DataStore;
import net.jami.jams.common.authmodule.AuthModuleKey;
import net.jami.jams.common.authentication.AuthenticationSource;
import net.jami.jams.common.authentication.AuthenticationSourceType;
import net.jami.jams.common.authmodule.AuthenticationResult;
import net.jami.jams.common.authmodule.AuthenticationModule;
import net.jami.jams.common.cryptoengineapi.CertificateAuthority;
import net.jami.jams.common.dao.StatementElement;
import net.jami.jams.common.dao.StatementList;
import net.jami.jams.common.objects.roots.X509Fields;
import net.jami.jams.common.objects.user.AccessLevel;
import net.jami.jams.common.objects.user.User;
import net.jami.jams.server.core.workflows.RegisterUserFlow;
import net.jami.jams.server.startup.LibraryLoader;
import net.jami.jams.common.utils.LibraryLoader;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import static net.jami.jams.server.Server.dataStore;
@Slf4j
@Getter
public class UserAuthenticationModule {
public class UserAuthenticationModule implements AuthenticationModule {
//This contains the DOMAIN-SOURCE.
//In general there is at most 2 here.
private final HashMap<AuthModuleKey, AuthenticationSource> authenticationSources = new HashMap<>();
private final static String LDAP_CONNECTOR_CLASS = "net.jami.jams.ldap.connector.LDAPConnector";
private final static String AD_CONNECTOR_CLASS = "net.jami.jams.ad.connector.ADConnector";
public static DataStore datastore;
public static CertificateAuthority certificateAuthority;
private final TokenController tokenController = new TokenController();
//We should initialize the authentication sources here, based on configurations, by default,
//we only attach the basic one.
public UserAuthenticationModule() {
authenticationSources.put(new AuthModuleKey("LOCAL", AuthenticationSourceType.LOCAL), dataStore);
}
private final HashMap<AuthModuleKey, AuthenticationSource> authenticationSources = new HashMap<>();
public void attachAuthSource(String realm, AuthenticationSourceType type, AuthenticationSource source){
authenticationSources.put(new AuthModuleKey(realm,type), source);
public UserAuthenticationModule(DataStore dataStore, CertificateAuthority certificateAuthority) {
UserAuthenticationModule.datastore = dataStore;
UserAuthenticationModule.certificateAuthority = certificateAuthority;
}
public void attachLDAPAuthSource(String settings){
try {
Class<?> cls = LibraryLoader.classLoader.loadClass("net.jami.jams.ldap.connector.LDAPConnector");
AuthenticationSource source = (AuthenticationSource) cls.getConstructor(String.class).newInstance(settings);
authenticationSources.put(new AuthModuleKey(source.getInfo().getRealm(),source.getInfo().getAuthenticationSourceType()), source);
}
catch (Exception e){
log.error("Could not load an ldap directory connector with reason: " + e.toString());
@Override
public void attachAuthSource(AuthenticationSourceType type, String settings) {
switch (type){
case AD: loadAuthConnector(AD_CONNECTOR_CLASS,settings); break;
case LDAP: loadAuthConnector(LDAP_CONNECTOR_CLASS,settings); break;
default: break;
}
}
//Repeat the same technique for the LDAP connector.
public void attachADAuthSource(String settings){
private void loadAuthConnector(String className, String settings){
try {
Class<?> cls = LibraryLoader.classLoader.loadClass("net.jami.jams.ad.connector.ADConnector");
Class<?> cls = LibraryLoader.classLoader.loadClass(className);
AuthenticationSource source = (AuthenticationSource) cls.getConstructor(String.class).newInstance(settings);
authenticationSources.put(new AuthModuleKey(source.getInfo().getRealm(),source.getInfo().getAuthenticationSourceType()), source);
}
catch (Exception e){
log.error("Could not load an active directory connector with reason: " + e.toString());
log.error("Could not load connector " + className + " with reason: " + e.toString());
}
}
public boolean authenticateUser(String username, String password){
//The user could already exist, so we pull him up.
if(dataStore.userExists(username)){
@Override
public AuthenticationResult authenticateUser(String username, String password) {
AuthenticationResult res = new AuthenticationResult(false,null);
if(datastore.userExists(username)){
StatementList statementList = new StatementList();
StatementElement statementElement = new StatementElement("username","=",username,"");
statementList.addStatement(statementElement);
User user = dataStore.getUserDao().getObjects(statementList).get(0);
return authenticationSources.get(new AuthModuleKey(user.getRealm(),user.getUserType()))
.authenticate(username,password);
User user = datastore.getUserDao().getObjects(statementList).get(0);
res.setAuthenticated(authenticationSources.get(new AuthModuleKey(user.getRealm(),user.getUserType()))
.authenticate(username,password));
//Generate token
res.setToken(tokenController.generateToken(user.getUsername(),res.getExpires()));
return res;
}
//The second case is much more violent, because we don't know in advance "where" this user comes
//from, so we have to infer (this is only really true for "users", all others are usually pre-marked)
......@@ -71,14 +80,33 @@ public class UserAuthenticationModule {
if(authenticationSources.get(key).authenticate(username,password)){
User user = new User();
user.setUsername(username);
user.setAccessLevel(AccessLevel.USER);
user.setRealm(key.getRealm());
user.setUserType(key.getType());
if(key.getType().equals(AuthenticationSourceType.LOCAL)) user.setPassword(password);
dataStore.getUserDao().storeObject(RegisterUserFlow.createUser(user));
return true;
X509Fields x509Fields = new X509Fields();
x509Fields.setCommonName(username);
user.setX509Fields(x509Fields);
RegisterUserFlow.createUser(user);
res.setToken(tokenController.generateToken(user.getUsername(),res.getExpires()));
res.setAuthenticated(true);
return res;
}
}
return false;
return res;
}
@Override
public AuthenticationResult authenticateUser(X509Certificate[] certificates) {
return null;
}
@Override
public String validateAndGetUsername(String token) {
return tokenController.getUsernameFromToken(token);
}
@Override
public HashMap<AuthModuleKey, AuthenticationSource> getAuthSources(){
return authenticationSources;
}
}
module jams.ca {
exports net.jami.jams.ca;
exports net.jami.jams.ca.workers.csr.builders;
requires jams.common;
requires org.bouncycastle.pkix;
requires lombok;
......
......@@ -26,11 +26,11 @@ public class SystemAccountBuilder {
keyPairGenerator.initialize(4096);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
X509v3CertificateBuilder builder = new X509v3CertificateBuilder(
new X500Name("CN=" + systemAccount.getX509Fields().getDN()),
new X500Name(systemAccount.getX509Fields().getDN()),
new BigInteger(256, new SecureRandom()),
new Date(System.currentTimeMillis()),
new Date(System.currentTimeMillis() + JamsCA.caLifetime),
new X500Name("CN="+ systemAccount.getX509Fields().getDN()),
new X500Name(systemAccount.getX509Fields().getDN()),
SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded())
);
systemAccount.setPrivateKey(keyPair.getPrivate());
......
......@@ -22,6 +22,11 @@
<artifactId>bcpkix-jdk15on</artifactId>
<version>${bouncy.castle.version}</version>
</dependency>
<dependency>
<groupId>org.apache.xbean</groupId>
<artifactId>xbean-classloader</artifactId>
<version>${xbean.version}</version>
</dependency>
</dependencies>
</project>
\ No newline at end of file
......@@ -46,8 +46,10 @@ module jams.common {
opens net.jami.jams.common.authentication.activedirectory to ad.connector ;
exports net.jami.jams.common.authentication.ldap;
exports net.jami.jams.common.jami;
exports net.jami.jams.common.authmodule;
requires jdk.crypto.cryptoki;
requires java.base;
requires java.sql;
requires org.apache.xbean.classloader;
}
package net.jami.jams.server.core.usermanagement;
package net.jami.jams.common.authmodule;
import lombok.*;
import net.jami.jams.common.authentication.AuthenticationSourceType;
......
package net.jami.jams.common.authmodule;
import net.jami.jams.common.authentication.AuthenticationSource;
import net.jami.jams.common.authentication.AuthenticationSourceType;
import java.security.cert.X509Certificate;
import java.util.HashMap;
public interface AuthenticationModule {
void attachAuthSource(AuthenticationSourceType type, String settings);
AuthenticationResult authenticateUser(String username, String password);
AuthenticationResult authenticateUser(X509Certificate[] certificates);
String validateAndGetUsername(String token);
HashMap<AuthModuleKey, AuthenticationSource> getAuthSources();
}
package net.jami.jams.common.authmodule;
import com.jsoniter.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import net.jami.jams.common.authentication.AuthenticationSourceType;
@Getter
@Setter
@NoArgsConstructor
public class AuthenticationResult {
private Boolean authenticated;
private String token;
private Long expires;
public AuthenticationResult(Boolean authenticated, String token) {
this.authenticated = authenticated;
this.token = token;
this.expires = System.currentTimeMillis() + 15*60*1_000;
}
}
package net.jami.jams.common.authmodule;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class TokenHolder {
private String token;
private Long expires;
}
package net.jami.jams.server.startup;
package net.jami.jams.common.utils;
import lombok.extern.slf4j.Slf4j;
import net.jami.jams.server.Server;
import org.apache.xbean.classloader.JarFileClassLoader;
import java.io.File;
......@@ -13,7 +12,7 @@ public class LibraryLoader {
public static JarFileClassLoader classLoader;
public static void loadlibs(String libDir){
public static void loadlibs(String libDir, Class parentClass){
try {
File dependencyDirectory = new File(libDir);
File[] files = dependencyDirectory.listFiles();
......@@ -25,13 +24,10 @@ public class LibraryLoader {
log.info("Successfully loaded the library " + files[i]);
}
}
classLoader = new JarFileClassLoader("Scheduler CL" + System.currentTimeMillis(), urls.toArray(new URL[urls.size()]), Server.class.getClassLoader());
classLoader = new JarFileClassLoader("Scheduler CL" + System.currentTimeMillis(), urls.toArray(new URL[urls.size()]), parentClass.getClassLoader());
}
catch (Exception e){
log.error("Errors occured while trying to load libraries...");
}
}
}
......@@ -38,23 +38,12 @@
<version>${revision}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.xbean</groupId>
<artifactId>xbean-classloader</artifactId>
<version>${xbean.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${javax.servlet.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.jami</groupId>
<artifactId>jami-dht</artifactId>
<version>2.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
......
......@@ -10,9 +10,8 @@ module jams.server {
requires javassist;
requires datastore;
requires org.apache.xbean.classloader;
requires jami.dht;
exports net.jami.jams.server.servlets.general to org.apache.tomcat.embed.core;
exports net.jami.jams.server.servlets.filters to org.apache.tomcat.embed.core;
exports net.jami.jams.server.servlets.api.auth to org.apache.tomcat.embed.core;
exports net.jami.jams.server.servlets.web to org.apache.tomcat.embed.core;
exports net.jami.jams.server.servlets.api.auth.login to org.apache.tomcat.embed.core;
exports net.jami.jams.server.servlets.api.auth.device to org.apache.tomcat.embed.core;
}
\ No newline at end of file
......@@ -2,16 +2,22 @@ package net.jami.jams.server;
import lombok.extern.slf4j.Slf4j;
import net.jami.datastore.main.DataStore;
import net.jami.jams.ca.JamsCA;
import net.jami.jams.ca.workers.csr.builders.SystemAccountBuilder;
import net.jami.jams.common.authentication.AuthenticationSource;
import net.jami.jams.common.authentication.AuthenticationSourceType;
import net.jami.jams.common.authmodule.AuthenticationModule;
import net.jami.jams.common.cryptoengineapi.CertificateAuthority;
import net.jami.jams.common.objects.roots.X509Fields;
import net.jami.jams.common.objects.system.SystemAccount;
import net.jami.jams.common.objects.system.SystemAccountType;
import net.jami.jams.common.objects.user.UserProfile;
import net.jami.jams.common.serialization.JsoniterRegistry;
import net.jami.jams.server.core.TomcatLauncher;
import net.jami.jams.server.core.usermanagement.AuthModuleKey;
import net.jami.jams.server.core.usermanagement.UserAuthenticationModule;
import net.jami.jams.common.authmodule.AuthModuleKey;
import net.jami.jams.server.startup.AuthModuleLoader;
import net.jami.jams.server.startup.CryptoEngineLoader;
import net.jami.jams.server.startup.LibraryLoader;
import net.jami.jams.common.utils.LibraryLoader;
import java.io.File;
import java.io.InputStream;
......@@ -34,7 +40,7 @@ public class Server {
public static DataStore dataStore;
//This one gets loaded via JAR, to make it more flexible.
public static CertificateAuthority certificateAuthority;
public static UserAuthenticationModule userAuthenticationModule;
public static AuthenticationModule userAuthenticationModule;
public static void main(String[] args) {
//Start tomcat.
......@@ -42,24 +48,37 @@ public class Server {
tomcatLauncher.startServer();
//Pre-load the libraries we should pre-load.
LibraryLoader.loadlibs("libs");
LibraryLoader.loadlibs("libs",Server.class);
//Step 1: Create the data store.
dataStore = new DataStore("jdbc:derby:jams;create=true");
userAuthenticationModule = new UserAuthenticationModule();
//Test block
//Step 2: if the server is initialized,
certificateAuthority = CryptoEngineLoader.loadCertificateAuthority(dataStore);
userAuthenticationModule = AuthModuleLoader.loadAuthenticationModule(dataStore,certificateAuthority);
isInstalled.set(new File(System.getProperty("user.dir") + File.separator + "config.json").exists());
log.info("Server is already installed: " + isInstalled.get());
//nasty injection to test this flow.
//Okay this is me cheating again heavily, just for testing purposes.
try {
JamsCA.serverDomain = "https://localhost";
JamsCA.signingAlgorithm = "SHA512WITHRSA";
SystemAccount caAccount = new SystemAccount();
caAccount.setSystemAccountType(SystemAccountType.CA);
caAccount.setX509Fields(new X509Fields());
caAccount.getX509Fields().setCommonName("Test CA");
caAccount.getX509Fields().setCountry("FR");
caAccount = SystemAccountBuilder.generateCA(caAccount);
JamsCA.CA = caAccount;
InputStream path;
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
path = classLoader.getResourceAsStream("ldapconfig.json");
userAuthenticationModule.attachLDAPAuthSource(new String(path.readAllBytes()));
UserProfile[] userProfile = userAuthenticationModule.getAuthenticationSources()
userAuthenticationModule.attachAuthSource(AuthenticationSourceType.LDAP,new String(path.readAllBytes()));
UserProfile[] userProfile = userAuthenticationModule.getAuthSources()
.get(new AuthModuleKey("savoirfairelinux", AuthenticationSourceType.LDAP))
.getUserProfile("Félix","FULL_TEXT_NAME");
System.out.println(userProfile[0]);
......
package net.jami.jams.server.servlets.api.contacts;
package net.jami.jams.server.servlets.api.auth.contacts;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
......@@ -8,7 +8,7 @@ import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/api/contacts")
@WebServlet("/api/auth/contacts")
public class ContactServlet extends HttpServlet {
@Override
......
package net.jami.jams.server.servlets.api.auth;
package net.jami.jams.server.servlets.api.auth.device;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
......
package net.jami.jams.server.servlets.api.auth;
package net.jami.jams.server.servlets.api.auth.device;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
......
package net.jami.jams.server.servlets.api.directory;
package net.jami.jams.server.servlets.api.auth.directory;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
......@@ -8,7 +8,7 @@ import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/api/directory/search")
@WebServlet("/api/auth/directory/search")
public class SearchDirectoryServlet extends HttpServlet {
@Override
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment