diff --git a/authentication-module/pom.xml b/authentication-module/pom.xml index 4931363951cedd9ee4d805781a7caa3d6f8375af..dd588562c74960256ded28a61addb3ff6782398a 100644 --- a/authentication-module/pom.xml +++ b/authentication-module/pom.xml @@ -29,6 +29,11 @@ <version>${revision}</version> <scope>compile</scope> </dependency> + <dependency> + <groupId>com.nimbusds</groupId> + <artifactId>nimbus-jose-jwt</artifactId> + <version>${nimbus.jwt.version}</version> + </dependency> </dependencies> <build> diff --git a/authentication-module/src/main/java/module-info.java b/authentication-module/src/main/java/module-info.java new file mode 100644 index 0000000000000000000000000000000000000000..a38e6014bb4446ed7f1a02d47da098bb2cf76b2b --- /dev/null +++ b/authentication-module/src/main/java/module-info.java @@ -0,0 +1,7 @@ +module authentication.module { + requires jams.common; + requires nimbus.jose.jwt; + requires datastore; + requires lombok; + requires jami.dht; +} \ No newline at end of file diff --git a/authentication-module/src/main/java/net/jami/jams/authmodule/TokenController.java b/authentication-module/src/main/java/net/jami/jams/authmodule/TokenController.java index 82f19cdaad7d77a6525604b4624cfecbb8fb5326..03185c11042e4527ccedd0374fe22bceb027bae3 100644 --- a/authentication-module/src/main/java/net/jami/jams/authmodule/TokenController.java +++ b/authentication-module/src/main/java/net/jami/jams/authmodule/TokenController.java @@ -1,41 +1,55 @@ package net.jami.jams.authmodule; -import net.jami.jams.common.authmodule.TokenHolder; +import com.nimbusds.jose.JOSEObjectType; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jose.crypto.RSASSASigner; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; +import net.jami.jams.common.authmodule.AuthTokenResponse; +import net.jami.jams.common.objects.user.User; -import java.util.Timer; -import java.util.TimerTask; +import java.security.PrivateKey; +import java.util.Date; import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -public class TokenController extends Timer { +public class TokenController{ - private ConcurrentHashMap<String, TokenHolder> tokenMap = new ConcurrentHashMap<>(); + private static PrivateKey signingKey; - 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 TokenController(PrivateKey signingKey) { + TokenController.signingKey = signingKey; } - public String generateToken(String username, Long expiry){ - String token = UUID.randomUUID().toString(); - tokenMap.put(username,new TokenHolder(token,expiry)); - return token; + public AuthTokenResponse getToken(User user) { + AuthTokenResponse authTokenResponse = new AuthTokenResponse(); + JWSHeader jwsHeader = new JWSHeader.Builder(JWSAlgorithm.RS256).type(JOSEObjectType.JWT).build(); + JWTClaimsSet jwtClaims = new JWTClaimsSet.Builder() + .issuer("JAMS") + .subject(user.getUsername()) + .audience("JAMS") + .claim("scope",user.getAccessLevel()) + .expirationTime(new Date(System.currentTimeMillis() + 30*60*1000)) + .notBeforeTime(new Date(System.currentTimeMillis())) + .issueTime(new Date(System.currentTimeMillis())) + .jwtID(UUID.randomUUID().toString()) + .build(); + SignedJWT signedJWT = new SignedJWT(jwsHeader, jwtClaims); + try { + signedJWT.sign(new RSASSASigner(signingKey)); + authTokenResponse.setAccess_token(signedJWT.serialize()); + authTokenResponse.setExpires_in(30*60*1000L); + authTokenResponse.setScope(user.getAccessLevel()); + authTokenResponse.setToken_type("Bearer"); + return authTokenResponse; + } + catch (Exception e){ + return null; + } } - public String getUsernameFromToken(String token){ - StringBuilder username = new StringBuilder(); - tokenMap.forEach( (k,v) -> { - if(v.getToken().equals(token)){ - username.append(k); - } - }); - if(username.length() != 0) return username.toString(); - return null; - } + + + + } diff --git a/authentication-module/src/main/java/net/jami/jams/authmodule/UserAuthenticationModule.java b/authentication-module/src/main/java/net/jami/jams/authmodule/UserAuthenticationModule.java index 1ec6234505b31702e519583ed2ce1fa1183623ed..b9d6b9e8335438ca7f58c4c8bb7564ce624578c5 100644 --- a/authentication-module/src/main/java/net/jami/jams/authmodule/UserAuthenticationModule.java +++ b/authentication-module/src/main/java/net/jami/jams/authmodule/UserAuthenticationModule.java @@ -5,8 +5,8 @@ import net.jami.datastore.main.DataStore; import net.jami.jams.common.authentication.AuthenticationSource; import net.jami.jams.common.authentication.AuthenticationSourceType; import net.jami.jams.common.authmodule.AuthModuleKey; +import net.jami.jams.common.authmodule.AuthTokenResponse; import net.jami.jams.common.authmodule.AuthenticationModule; -import net.jami.jams.common.authmodule.AuthenticationResult; import net.jami.jams.common.cryptoengineapi.CertificateAuthority; import net.jami.jams.common.dao.StatementElement; import net.jami.jams.common.dao.StatementList; @@ -14,8 +14,15 @@ import net.jami.jams.common.jami.NameServer; import net.jami.jams.common.objects.user.AccessLevel; import net.jami.jams.common.objects.user.User; import net.jami.jams.common.utils.LibraryLoader; +import net.jami.jams.common.utils.X509Utils; +import java.io.*; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.PublicKey; import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPublicKey; import java.util.HashMap; @@ -27,15 +34,53 @@ public class UserAuthenticationModule implements AuthenticationModule { 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(); + private final TokenController tokenController; + private PrivateKey privateKey = null; + private PublicKey publicKey = null; + private final HashMap<AuthModuleKey, AuthenticationSource> authenticationSources = new HashMap<>(); - public UserAuthenticationModule(DataStore dataStore, CertificateAuthority certificateAuthority) { + public UserAuthenticationModule(DataStore dataStore, CertificateAuthority certificateAuthority) throws Exception{ UserAuthenticationModule.datastore = dataStore; UserAuthenticationModule.certificateAuthority = certificateAuthority; authenticationSources.put(new AuthModuleKey("LOCAL",AuthenticationSourceType.LOCAL),datastore); log.info("Started authentication module - default local source is already enabled!"); + + File pubkeyFile = new File(System.getProperty("user.dir") + File.separator + "oauth.pub"); + File privateKeyFile = new File(System.getProperty("user.dir") + File.separator + "oauth.key"); + + if(!privateKeyFile.exists() || !pubkeyFile.exists()){ + log.info("Generating first time private/public keys for OAuth!"); + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(4096); + KeyPair kp = keyPairGenerator.generateKeyPair(); + privateKey = kp.getPrivate(); + publicKey = kp.getPublic(); + //Store these to file. + OutputStream os; + os = new FileOutputStream(System.getProperty("user.dir") + File.separator + "oauth.key"); + os.write(X509Utils.getPEMStringFromPrivateKey(privateKey).getBytes()); + os.flush(); + os.close(); + log.info("Succesfully stored OAuth private key for future use..."); + os = new FileOutputStream(System.getProperty("user.dir") + File.separator + "oauth.pub"); + os.write(X509Utils.getPEMStringFromPubKey(publicKey).getBytes()); + os.flush(); + os.close(); + log.info("Succesfully stored OAuth public key for future use..."); + } + else{ + InputStream path; + privateKey = X509Utils.getKeyFromPEMString(new String(new FileInputStream(privateKeyFile).readAllBytes())); + log.info("Succesfully loaded OAuth private key!"); + publicKey = X509Utils.getPubKeyFromPEMString(new String(new FileInputStream(pubkeyFile).readAllBytes())); + log.info("Succesfully loaded OAuth public key!"); + } + //TODO: Read signing key, if file does not exist create it (also create the corresponding public key) + tokenController = new TokenController(privateKey); + //Also expose the public key programatically. + log.info("OAuth2 Token System instantiated succesfully!"); } @Override @@ -59,18 +104,16 @@ public class UserAuthenticationModule implements AuthenticationModule { } @Override - public AuthenticationResult authenticateUser(String username, String password) { - AuthenticationResult res = new AuthenticationResult(false,null); + public AuthTokenResponse authenticateUser(String username, String password) { + AuthTokenResponse res = 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); - 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; + if(authenticationSources.get(new AuthModuleKey(user.getRealm(),user.getUserType())) + .authenticate(username,password)) + return tokenController.getToken(user); } //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) @@ -83,24 +126,17 @@ public class UserAuthenticationModule implements AuthenticationModule { user.setRealm(key.getRealm()); user.setUserType(key.getType()); RegisterUserFlow.createUser(user,null); - res.setToken(tokenController.generateToken(user.getUsername(),res.getExpires())); - res.setAuthenticated(true); - return res; + return tokenController.getToken(user); } } return res; } @Override - public AuthenticationResult authenticateUser(X509Certificate[] certificates) { + public AuthTokenResponse authenticateUser(X509Certificate[] certificates) { return null; } - @Override - public String validateAndGetUsername(String token) { - return tokenController.getUsernameFromToken(token); - } - @Override public HashMap<AuthModuleKey, AuthenticationSource> getAuthSources(){ return authenticationSources; @@ -130,4 +166,9 @@ public class UserAuthenticationModule implements AuthenticationModule { return RegisterUserFlow.createUser(user,nameServer); } + @Override + public RSAPublicKey getAuthModulePubKey() { + return (RSAPublicKey) publicKey; + } + } diff --git a/jams-common/src/main/java/net/jami/jams/common/authmodule/AuthTokenResponse.java b/jams-common/src/main/java/net/jami/jams/common/authmodule/AuthTokenResponse.java new file mode 100644 index 0000000000000000000000000000000000000000..8d76f0698bc18901d72e83e4a6867dfd6190816b --- /dev/null +++ b/jams-common/src/main/java/net/jami/jams/common/authmodule/AuthTokenResponse.java @@ -0,0 +1,14 @@ +package net.jami.jams.common.authmodule; + +import lombok.Getter; +import lombok.Setter; +import net.jami.jams.common.objects.user.AccessLevel; + +@Getter +@Setter +public class AuthTokenResponse { + private String token_type; + private String access_token; + private Long expires_in; + private AccessLevel scope; +} diff --git a/jams-common/src/main/java/net/jami/jams/common/authmodule/AuthenticationModule.java b/jams-common/src/main/java/net/jami/jams/common/authmodule/AuthenticationModule.java index 4facbf632c5eef59cf02ba17f1a1862379bf9d25..a063b77da7148136e00e45bf9462c391cac2293e 100644 --- a/jams-common/src/main/java/net/jami/jams/common/authmodule/AuthenticationModule.java +++ b/jams-common/src/main/java/net/jami/jams/common/authmodule/AuthenticationModule.java @@ -6,15 +6,16 @@ import net.jami.jams.common.jami.NameServer; import net.jami.jams.common.objects.user.User; import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPublicKey; 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); + AuthTokenResponse authenticateUser(String username, String password); + AuthTokenResponse authenticateUser(X509Certificate[] certificates); HashMap<AuthModuleKey, AuthenticationSource> getAuthSources(); boolean testModuleConfiguration(AuthenticationSourceType type, String configuration); boolean createUser(AuthenticationSourceType type, String realm, NameServer nameServer, User user); + RSAPublicKey getAuthModulePubKey(); } diff --git a/jams-common/src/main/java/net/jami/jams/common/objects/user/UserProfile.java b/jams-common/src/main/java/net/jami/jams/common/objects/user/UserProfile.java index 19f47b105e2016d89d4b9f9c5cb3fc612894f695..03130cf7fd94f4d9e235cc53d791789db2c46f38 100644 --- a/jams-common/src/main/java/net/jami/jams/common/objects/user/UserProfile.java +++ b/jams-common/src/main/java/net/jami/jams/common/objects/user/UserProfile.java @@ -10,16 +10,14 @@ import java.util.HashMap; @Setter public class UserProfile { - public static HashMap<String, Method> exposedMethods = new HashMap<>(); - static{ - for(Method method: UserProfile.class.getMethods()){ - exposedMethods.put(method.getName(),method); + static { + for (Method method : UserProfile.class.getMethods()) { + exposedMethods.put(method.getName(), method); } } - private String firstName; private String lastName; private String phoneNumber; @@ -31,5 +29,4 @@ public class UserProfile { private String organization; - } diff --git a/jams-common/src/main/java/net/jami/jams/common/utils/X509Utils.java b/jams-common/src/main/java/net/jami/jams/common/utils/X509Utils.java index 3eb5068a61730a30a33b636ff1a49b62fbba5ed5..15c269dee38316d88bd643cbeb026b0533536e58 100644 --- a/jams-common/src/main/java/net/jami/jams/common/utils/X509Utils.java +++ b/jams-common/src/main/java/net/jami/jams/common/utils/X509Utils.java @@ -2,6 +2,7 @@ package net.jami.jams.common.utils; import lombok.extern.slf4j.Slf4j; import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.openssl.PEMKeyPair; import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; @@ -11,6 +12,7 @@ import java.io.*; import java.nio.charset.StandardCharsets; import java.security.KeyFactory; import java.security.PrivateKey; +import java.security.PublicKey; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.spec.PKCS8EncodedKeySpec; @@ -23,6 +25,8 @@ public class X509Utils { private static final String PVK_TAIL = "\n-----END PRIVATE KEY-----"; private static final String CERT_HEADER = "-----BEGIN CERTIFICATE-----\n"; private static final String CERT_TAIL = "\n-----END CERTIFICATE-----"; + private static final String PPK_HEADER = "-----BEGIN PUBLIC KEY-----\n"; + private static final String PPK_TAIL = "\n-----END PUBLIC KEY-----"; public static PrivateKey getKeyFromPEMString(String keyString) { try { @@ -42,6 +46,26 @@ public class X509Utils { } } + public static PublicKey getPubKeyFromPEMString(String keyString){ + try { + PEMParser parser = new PEMParser(new StringReader(keyString)); + Object parsedObject = parser.readObject(); + if (parsedObject instanceof PEMKeyPair) { + PEMKeyPair pk = (PEMKeyPair) parsedObject; + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pk.getPublicKeyInfo().getEncoded()); + return KeyFactory.getInstance("RSA").generatePublic(keySpec); + } else { + JcaPEMKeyConverter converter = new JcaPEMKeyConverter(); + return converter.getPublicKey((SubjectPublicKeyInfo) parsedObject); + } + } + catch (Exception e){ + log.error("And error has occurred reading the public key from string!"); + return null; + } + } + + public static X509Certificate getCertificateFromPEMString(String certificateString) { try { CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); @@ -74,7 +98,20 @@ public class X509Utils { stringBuilder.append(CERT_TAIL); return stringBuilder.toString(); } catch (Exception e) { - log.error("An error has occured trying to convert the Private Key to PEM, stack trace: " + e.toString()); + log.error("An error has occured trying to convert the Certificate Key to PEM, stack trace: " + e.toString()); + return null; + } + } + + public static String getPEMStringFromPubKey(PublicKey publicKey){ + StringBuilder stringBuilder = new StringBuilder(); + try { + stringBuilder.append(PPK_HEADER); + stringBuilder.append(Base64.getEncoder().encodeToString(publicKey.getEncoded())); + stringBuilder.append(PPK_TAIL); + return stringBuilder.toString(); + } catch (Exception e) { + log.error("An error has occured trying to convert the Public Key to PEM, stack trace: " + e.toString()); return null; } } diff --git a/jams-server/pom.xml b/jams-server/pom.xml index c1d18a958625169b5dd0b01367ee8fcd99401a7e..5a9964471d0569db03b38be85319dc760d381eca 100644 --- a/jams-server/pom.xml +++ b/jams-server/pom.xml @@ -47,15 +47,20 @@ <dependency> <groupId>net.jami</groupId> <artifactId>jami-nameserver</artifactId> - <version>2.0</version> + <version>${revision}</version> <scope>compile</scope> </dependency> <dependency> <groupId>net.jami</groupId> <artifactId>jami-dht</artifactId> - <version>2.0</version> + <version>${revision}</version> <scope>compile</scope> </dependency> + <dependency> + <groupId>com.nimbusds</groupId> + <artifactId>nimbus-jose-jwt</artifactId> + <version>${nimbus.jwt.version}</version> + </dependency> </dependencies> <build> diff --git a/jams-server/src/main/java/module-info.java b/jams-server/src/main/java/module-info.java index e06d3e94676f5dd5d84ac8fd8751afdabcd206e2..266f35d04d0bfc44cb26030d1e8f472a7e80b941 100644 --- a/jams-server/src/main/java/module-info.java +++ b/jams-server/src/main/java/module-info.java @@ -12,6 +12,7 @@ module jams.server { requires org.apache.xbean.classloader; requires jami.nameserver; requires jami.dht; + requires nimbus.jose.jwt; 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.login to org.apache.tomcat.embed.core; diff --git a/jams-server/src/main/java/net/jami/jams/server/servlets/api/auth/login/AuthRequestProcessor.java b/jams-server/src/main/java/net/jami/jams/server/servlets/api/auth/login/AuthRequestProcessor.java index 79cf481976caf44e75e378586325c79d97bcfadd..c10165430dc33943f38757d19b275d6238585ef5 100644 --- a/jams-server/src/main/java/net/jami/jams/server/servlets/api/auth/login/AuthRequestProcessor.java +++ b/jams-server/src/main/java/net/jami/jams/server/servlets/api/auth/login/AuthRequestProcessor.java @@ -1,6 +1,6 @@ package net.jami.jams.server.servlets.api.auth.login; -import net.jami.jams.common.authmodule.AuthenticationResult; +import net.jami.jams.common.authmodule.AuthTokenResponse; import java.security.cert.X509Certificate; @@ -9,19 +9,19 @@ import static net.jami.jams.server.Server.userAuthenticationModule; public class AuthRequestProcessor { //This case does not talk to the authentication module, only to the ca - public static AuthenticationResult processX509Auth(X509Certificate[] certificates){ + public static AuthTokenResponse processX509Auth(X509Certificate[] certificates){ return userAuthenticationModule.authenticateUser(certificates); } - public static AuthenticationResult processUsernamePasswordAuth(String username, String password){ + public static AuthTokenResponse processUsernamePasswordAuth(String username, String password){ return userAuthenticationModule.authenticateUser(username,password); } - public static AuthenticationResult processUsernamePasswordAuth(String authorization){ + public static AuthTokenResponse processUsernamePasswordAuth(String authorization){ String[] credentials = Decoders.decodeAuthHeader(authorization); if(credentials != null && credentials.length == 2) { return userAuthenticationModule.authenticateUser(credentials[0], credentials[1]); } - return new AuthenticationResult(false,null); + return null; } } diff --git a/jams-server/src/main/java/net/jami/jams/server/servlets/api/auth/login/LoginServlet.java b/jams-server/src/main/java/net/jami/jams/server/servlets/api/auth/login/LoginServlet.java index aeb67a092eb21e5604ee12cec4f28388879a5e59..0c2374a296842c708429335a1c1f351874416da3 100644 --- a/jams-server/src/main/java/net/jami/jams/server/servlets/api/auth/login/LoginServlet.java +++ b/jams-server/src/main/java/net/jami/jams/server/servlets/api/auth/login/LoginServlet.java @@ -6,7 +6,7 @@ import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import net.jami.jams.common.authmodule.AuthenticationResult; +import net.jami.jams.common.authmodule.AuthTokenResponse; import java.io.IOException; import java.security.cert.X509Certificate; @@ -22,7 +22,7 @@ public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //There are 3 possible cases here. //Case 1: form submitted username/password - AuthenticationResult res = null; + AuthTokenResponse res = null; if(req.getParameter("username") != null && req.getParameter("password") != null){ res = processUsernamePasswordAuth(req.getParameter("username"),req.getParameter("password")); } @@ -35,6 +35,7 @@ public class LoginServlet extends HttpServlet { res = processX509Auth((X509Certificate[])req.getAttribute("jakarta.servlet.request.X509Certificate")); } //Here we catch the need to create a user (this is only possible in 1 & 2). - resp.getOutputStream().write(JsonStream.serialize(res).getBytes()); + if(res == null) resp.sendError(403,"Invalid credentials!"); + else resp.getOutputStream().write(JsonStream.serialize(res).getBytes()); } } diff --git a/jams-server/src/main/java/net/jami/jams/server/servlets/api/install/StartInstallServlet.java b/jams-server/src/main/java/net/jami/jams/server/servlets/api/install/StartInstallServlet.java index c5e451e89ea88dfdb8a6adaa0906dc92accbd2e4..a41aaac61f45d84a90ba852d641cf0479151a24a 100644 --- a/jams-server/src/main/java/net/jami/jams/server/servlets/api/install/StartInstallServlet.java +++ b/jams-server/src/main/java/net/jami/jams/server/servlets/api/install/StartInstallServlet.java @@ -8,7 +8,7 @@ import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import net.jami.jams.common.authentication.AuthenticationSourceType; -import net.jami.jams.common.authmodule.AuthenticationResult; +import net.jami.jams.common.authmodule.AuthTokenResponse; import net.jami.jams.common.dao.StatementList; import net.jami.jams.common.objects.requests.CredentialsRequest; import net.jami.jams.common.objects.user.AccessLevel; @@ -34,12 +34,13 @@ public class StartInstallServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { CredentialsRequest credentialsRequest = JsonIterator.deserialize(req.getInputStream().readAllBytes(),CredentialsRequest.class); - AuthenticationResult res = null; + AuthTokenResponse res = null; if(credentialsRequest.getUsername() != null && credentialsRequest.getPassword() != null){ res = processUsernamePasswordAuth(credentialsRequest.getUsername(),credentialsRequest.getPassword()); } resp.setHeader("endpoint",CachedObjects.endpoint); - resp.getOutputStream().write(JsonStream.serialize(res).getBytes()); + if(res == null) resp.sendError(403,"Could not authenticate!"); + else resp.getOutputStream().write(JsonStream.serialize(res).getBytes()); } //This is the ONLY case where we write directy to the DB @@ -60,7 +61,7 @@ public class StartInstallServlet extends HttpServlet { user.setRealm("LOCAL"); user.setAccessLevel(AccessLevel.ADMIN); dataStore.getUserDao().storeObject(user); - AuthenticationResult res = processUsernamePasswordAuth(user.getUsername(),user.getPassword()); + AuthTokenResponse res = processUsernamePasswordAuth(user.getUsername(),user.getPassword()); resp.getOutputStream().write(JsonStream.serialize(res).getBytes()); CachedObjects.endpoint = "/api/install/ca"; } diff --git a/jams-server/src/main/java/net/jami/jams/server/servlets/filters/APIFilter.java b/jams-server/src/main/java/net/jami/jams/server/servlets/filters/APIFilter.java index a3b674066f8d987a926cf3634115a038b946a1ac..d634a266aef8ec99b7edadcefc51e79f60911afa 100644 --- a/jams-server/src/main/java/net/jami/jams/server/servlets/filters/APIFilter.java +++ b/jams-server/src/main/java/net/jami/jams/server/servlets/filters/APIFilter.java @@ -1,5 +1,8 @@ package net.jami.jams.server.servlets.filters; +import com.nimbusds.jose.JWSVerifier; +import com.nimbusds.jose.crypto.RSASSAVerifier; +import com.nimbusds.jwt.SignedJWT; import jakarta.servlet.*; import jakarta.servlet.annotation.WebFilter; import jakarta.servlet.http.HttpServletRequest; @@ -8,6 +11,7 @@ import lombok.extern.slf4j.Slf4j; import net.jami.jams.server.Server; import java.io.IOException; +import java.util.Date; import static net.jami.jams.server.Server.userAuthenticationModule; @@ -25,10 +29,21 @@ public class APIFilter implements Filter { boolean authsuccess = false; boolean isLogin = false; if (request.getServletPath().contains("login")) isLogin = true; - if (request.getHeader("x-token") != null) - authsuccess = userAuthenticationModule.validateAndGetUsername(request.getHeader("x-token")) != null; + if (request.getHeader("Bearer") != null){ + SignedJWT signedJWT = null; + try { + JWSVerifier jwsVerifier = new RSASSAVerifier(userAuthenticationModule.getAuthModulePubKey()); + signedJWT = SignedJWT.parse(request.getHeader("Bearer")); + if(signedJWT.verify(jwsVerifier) && signedJWT.getJWTClaimsSet().getExpirationTime().compareTo(new Date()) > 0){ + authsuccess = true; + request.setAttribute("username",signedJWT.getJWTClaimsSet().getSubject()); + request.setAttribute("accessLevel",signedJWT.getJWTClaimsSet().getClaim("scope")); + } + } catch (Exception e) { + log.info("Received an invalid token, declining access..."); + } + } if (authsuccess || isLogin){ - servletRequest.setAttribute("username",userAuthenticationModule.validateAndGetUsername(request.getHeader("x-token"))); filterChain.doFilter(servletRequest, servletResponse); } else response.sendError(403,"This endpoint requires setup to be complete!"); diff --git a/jams-server/src/main/java/net/jami/jams/server/servlets/filters/InstallFilter.java b/jams-server/src/main/java/net/jami/jams/server/servlets/filters/InstallFilter.java index 2fcff582848ee261682f821b323b3c7712985037..696cec90a72cfd6510ce73d3bd3d3eb51a490f8b 100644 --- a/jams-server/src/main/java/net/jami/jams/server/servlets/filters/InstallFilter.java +++ b/jams-server/src/main/java/net/jami/jams/server/servlets/filters/InstallFilter.java @@ -1,16 +1,22 @@ package net.jami.jams.server.servlets.filters; +import com.nimbusds.jose.JWSVerifier; +import com.nimbusds.jose.crypto.RSASSAVerifier; +import com.nimbusds.jwt.SignedJWT; import jakarta.servlet.*; import jakarta.servlet.annotation.WebFilter; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; import net.jami.jams.server.Server; import java.io.IOException; +import java.util.Date; import static net.jami.jams.server.Server.userAuthenticationModule; @WebFilter("/api/install/*") +@Slf4j public class InstallFilter implements Filter { @Override @@ -24,8 +30,17 @@ public class InstallFilter implements Filter { boolean authsuccess = false; boolean isLogin = false; if(request.getServletPath().contains("start")) isLogin = true; - if(request.getHeader("x-token") != null) { - authsuccess = userAuthenticationModule.validateAndGetUsername(request.getHeader("x-token")) != null; + SignedJWT signedJWT = null; + try { + JWSVerifier jwsVerifier = new RSASSAVerifier(userAuthenticationModule.getAuthModulePubKey()); + signedJWT = SignedJWT.parse(request.getHeader("Bearer")); + if(signedJWT.verify(jwsVerifier) && signedJWT.getJWTClaimsSet().getExpirationTime().compareTo(new Date()) > 0){ + authsuccess = true; + request.setAttribute("username",signedJWT.getJWTClaimsSet().getSubject()); + request.setAttribute("accessLevel",signedJWT.getJWTClaimsSet().getClaim("scope")); + } + } catch (Exception e) { + log.info("Received an invalid token, declining access..."); } if(authsuccess || isLogin) filterChain.doFilter(servletRequest,servletResponse); else response.sendError(403,"You are not authorized to access this page!"); diff --git a/jams-server/src/main/java/net/jami/jams/server/startup/AuthModuleLoader.java b/jams-server/src/main/java/net/jami/jams/server/startup/AuthModuleLoader.java index 0687fe5d27425c4480f1fc16498a069c84a00f3e..c7444ca483ed701a721755d70776a7d94eab0628 100644 --- a/jams-server/src/main/java/net/jami/jams/server/startup/AuthModuleLoader.java +++ b/jams-server/src/main/java/net/jami/jams/server/startup/AuthModuleLoader.java @@ -9,16 +9,15 @@ import net.jami.jams.common.utils.LibraryLoader; @Slf4j public class AuthModuleLoader { - public static AuthenticationModule loadAuthenticationModule(DataStore dataStore, CertificateAuthority certificateAuthority ){ + public static AuthenticationModule loadAuthenticationModule(DataStore dataStore, CertificateAuthority certificateAuthority) { try { Class<?> cls = LibraryLoader.classLoader.loadClass("net.jami.jams.authmodule.UserAuthenticationModule"); AuthenticationModule authenticationModule = - (AuthenticationModule) cls.getConstructor(DataStore.class,CertificateAuthority.class) - .newInstance(dataStore,certificateAuthority); + (AuthenticationModule) cls.getConstructor(DataStore.class, CertificateAuthority.class) + .newInstance(dataStore, certificateAuthority); log.info("Loaded Authentication Module succesfully!"); return authenticationModule; - } - catch (Exception e){ + } catch (Exception e) { log.error("Unable to load Authentication Module!"); return null; } diff --git a/pom.xml b/pom.xml index 6dcc90ac1c688a0b0f8086fd08bdaaeab10a2095..bd0f1c4265f1b4ff8ba0a7c8bc9c2cbe459bd6b1 100644 --- a/pom.xml +++ b/pom.xml @@ -44,6 +44,7 @@ <ldaptive.version>2.0.0-RC4</ldaptive.version> <javax.servlet.version>4.0.1</javax.servlet.version> <maven.clean.version>3.1.0</maven.clean.version> + <nimbus.jwt.version>8.17</nimbus.jwt.version> </properties> <dependencies>