231 lines
8.3 KiB
Java
Executable File
231 lines
8.3 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);
|
|
|
|
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
|
|
|
|
// Check the password hash
|
|
if (!PasswordUtil.validate(credentials.getPassword(), user.getPasswordHash())) {
|
|
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) {
|
|
// Creation of 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));
|
|
}
|
|
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));
|
|
}
|
|
}
|
|
// 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());
|
|
}
|
|
}
|