This repository has been archived on 2021-12-05. You can view files and clone it, but cannot push or open issues or pull requests.
envoy/server/src/main/java/envoy/server/processors/LoginCredentialProcessor.java

238 lines
8.4 KiB
Java
Executable File

package envoy.server.processors;
import static envoy.data.Message.MessageStatus.*;
import static envoy.data.User.UserStatus.ONLINE;
import static envoy.event.HandshakeRejection.*;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.logging.Logger;
import javax.persistence.NoResultException;
import envoy.data.LoginCredentials;
import envoy.event.*;
import envoy.event.contact.ContactsChangedSinceLastLogin;
import envoy.util.*;
import envoy.server.data.*;
import envoy.server.net.*;
import envoy.server.util.*;
/**
* This {@link ObjectProcessor} handles {@link LoginCredentials}.
*
* @author Kai S. K. Engelbart
* @author Maximilian Käfer
* @since Envoy Server Standalone v0.1-alpha
*/
public final class LoginCredentialProcessor implements ObjectProcessor<LoginCredentials> {
private final PersistenceManager persistenceManager = PersistenceManager.getInstance();
private final ConnectionManager connectionManager = ConnectionManager.getInstance();
private static final Logger logger = EnvoyLog.getLogger(LoginCredentialProcessor.class);
@Override
public void process(LoginCredentials credentials, long socketID, ObjectWriteProxy writeProxy) {
// Cache this write proxy for user-independent notifications
UserStatusChangeProcessor.setWriteProxy(writeProxy);
// Check for compatible versions
if (!VersionUtil.verifyCompatibility(credentials.getClientVersion())) {
logger.info("The client has the wrong version.");
writeProxy.write(socketID, new HandshakeRejection(WRONG_VERSION));
return;
}
// Acquire a user object (or reject the handshake if that's impossible)
User user = null;
if (!credentials.isRegistration())
try {
user = persistenceManager.getUserByName(credentials.getIdentifier());
// Check if the user is already online
if (connectionManager.isOnline(user.getID())) {
logger.warning(user + " is already online!");
writeProxy.write(socketID, new HandshakeRejection(INTERNAL_ERROR));
return;
}
// Authenticate with password or token
if (credentials.usesToken()) {
// Check the token
if (user.getAuthToken() == null
|| user.getAuthTokenExpiration().isBefore(Instant.now())
|| !user.getAuthToken().equals(credentials.getPassword())) {
logger.info(user + " tried to use an invalid token.");
writeProxy.write(socketID, new HandshakeRejection(INVALID_TOKEN));
return;
}
} else if (!PasswordUtil.validate(credentials.getPassword(),
user.getPasswordHash())) {
// Check the password hash
logger.info(user + " has entered the wrong password.");
writeProxy.write(socketID, new HandshakeRejection(WRONG_PASSWORD_OR_USER));
return;
}
} catch (final NoResultException e) {
logger.info("The requested user does not exist.");
writeProxy.write(socketID, new HandshakeRejection(WRONG_PASSWORD_OR_USER));
return;
}
else {
// Validate user name
if (!Bounds.isValidContactName(credentials.getIdentifier())) {
logger.info("The requested user name is not valid.");
writeProxy.write(socketID, new HandshakeRejection(INTERNAL_ERROR));
return;
}
try {
// Check if the name is taken
PersistenceManager.getInstance().getUserByName(credentials.getIdentifier());
// This code only gets executed if this user already exists
logger.info("The requested user already exists.");
writeProxy.write(socketID, new HandshakeRejection(USERNAME_TAKEN));
return;
} catch (final NoResultException e) {
// Create a new user
user = new User();
user.setName(credentials.getIdentifier());
user.setLastSeen(Instant.now());
user.setStatus(ONLINE);
user.setPasswordHash(PasswordUtil.hash(credentials.getPassword()));
user.setContacts(new HashSet<>());
user.setLatestContactDeletion(Instant.EPOCH);
persistenceManager.addContact(user);
logger.info("Registered new " + user);
}
}
logger.info(user + " successfully authenticated.");
connectionManager.registerUser(user.getID(), socketID);
// Change status and notify contacts about it
UserStatusChangeProcessor.updateUserStatus(user, ONLINE);
// Process token request
if (credentials.requestToken()) {
String token;
if (user.getAuthToken() != null && user.getAuthTokenExpiration().isAfter(Instant.now()))
// Reuse existing token and delay expiration date
token = user.getAuthToken();
else {
// Generate new token
token = AuthTokenGenerator.nextToken();
user.setAuthToken(token);
}
user.setAuthTokenExpiration(Instant.now().plus(
ServerConfig.getInstance().getAuthTokenExpiration().longValue(), ChronoUnit.DAYS));
persistenceManager.updateContact(user);
writeProxy.write(socketID, new NewAuthToken(token));
}
// Notify the user if a contact deletion has happened since he last logged in
if (user.getLatestContactDeletion().isAfter(user.getLastSeen()))
writeProxy.write(socketID, new ContactsChangedSinceLastLogin());
// Complete the handshake
writeProxy.write(socketID, user.toCommon());
// Send pending (group) messages and status changes
final var pendingMessages =
PersistenceManager.getInstance().getPendingMessages(user, credentials.getLastSync());
pendingMessages.removeIf(GroupMessage.class::isInstance);
logger.fine("Sending " + pendingMessages.size() + " pending messages to " + user + "...");
for (final var msg : pendingMessages) {
final var msgCommon = msg.toCommon();
if (msg.getCreationDate().isAfter(credentials.getLastSync()))
// Sync without side effects
writeProxy.write(socketID, msgCommon);
else if (msg.getStatus() == SENT) {
// Send the message
writeProxy.write(socketID, msgCommon);
msg.received();
PersistenceManager.getInstance().updateMessage(msg);
// Notify the sender about the delivery
if (connectionManager.isOnline(msg.getSender().getID())) {
msgCommon.nextStatus();
writeProxy.write(connectionManager.getSocketID(msg.getSender().getID()),
new MessageStatusChange(msgCommon));
}
} else {
writeProxy.write(socketID, new MessageStatusChange(msgCommon));
}
}
final List<GroupMessage> pendingGroupMessages = PersistenceManager.getInstance()
.getPendingGroupMessages(user, credentials.getLastSync());
logger.fine("Sending " + pendingGroupMessages.size() + " pending group messages to " + user
+ "...");
for (final var gmsg : pendingGroupMessages) {
final var gmsgCommon = gmsg.toCommon();
// Deliver the message to the user if he hasn't received it yet
if (gmsg.getCreationDate().isAfter(credentials.getLastSync())
|| gmsg.getMemberMessageStatus().get(user.getID()) == SENT) {
if (gmsg.getMemberMessageStatus().replace(user.getID(), RECEIVED) != RECEIVED) {
gmsg.setLastStatusChangeDate(Instant.now());
writeProxy.write(socketID, gmsgCommon);
// Notify all online group members about the status change
writeProxy.writeToOnlineContacts(gmsg.getRecipient().getContacts(),
new GroupMessageStatusChange(gmsg.getID(), RECEIVED, Instant.now(),
connectionManager.getUserIDBySocketID(socketID)));
if (Collections.min(gmsg.getMemberMessageStatus().values()) == RECEIVED) {
gmsg.received();
// Notify online members about the status change
writeProxy.writeToOnlineContacts(gmsg.getRecipient().getContacts(),
new MessageStatusChange(gmsg.getID(), gmsg.getStatus(), Instant.now()));
}
PersistenceManager.getInstance().updateMessage(gmsg);
} else {
// Just send the message without updating if it was received in the past
writeProxy.write(socketID, gmsgCommon);
}
} else {
// Sending group message status changes
if (gmsg.getStatus() == SENT
&& gmsg.getLastStatusChangeDate().isAfter(gmsg.getCreationDate())
|| gmsg.getStatus() == RECEIVED
&& gmsg.getLastStatusChangeDate().isAfter(gmsg.getReceivedDate()))
gmsg.getMemberMessageStatus()
.forEach((memberID, memberStatus) -> writeProxy.write(socketID,
new GroupMessageStatusChange(gmsg.getID(), memberStatus,
gmsg.getLastStatusChangeDate(), memberID)));
// Deliver just a status change instead of the whole message
if (gmsg.getStatus() == RECEIVED
&& user.getLastSeen().isBefore(gmsg.getReceivedDate())
|| gmsg.getStatus() == READ && user.getLastSeen().isBefore(gmsg.getReadDate()))
writeProxy.write(socketID, new MessageStatusChange(gmsgCommon));
}
}
}
}