/* * TestChatSession.java * * Created on Apr 15, 2009, 12:03:00 PM * * Description: . * * Copyright (C) Apr 15, 2009 Stephen L. Reed. * * This program is free software; you can redistribute it and/or modify it under the terms * of the GNU General Public License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program; * if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.texai.webserver; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.Random; import javax.mail.Message; import javax.mail.MessagingException; import javax.mail.Transport; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMultipart; import net.jcip.annotations.NotThreadSafe; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.jboss.netty.channel.Channel; import org.jboss.netty.handler.codec.http.HttpRequest; import org.texai.network.netty.utils.NettyHTTPUtils; import org.texai.util.OneWayEncryptionService; /** * * @author Stephen L. Reed */ @NotThreadSafe public class TestChatSession implements WebChatActions { /** the log4j logger */ private static final Logger LOGGER = Logger.getLogger(TestChatSession.class); /** the indicator whether debug logging is enabled */ private static final boolean IS_DEBUG_LOGGING_ENABLED = LOGGER.isDebugEnabled(); /** the indicator whether info logging is enabled */ private static final boolean IS_INFO_LOGGING_ENABLED = LOGGER.isEnabledFor(Level.INFO); /** the registered user dictionary, username --> registered user */ private final Map<String, RegisteredUser> registeredUserDictionaryByUsername = new HashMap<>(); /** the registered user dictionary, email address --> registered user */ private final Map<String, RegisteredUser> registeredUserDictionaryByEmailAddress = new HashMap<>(); /** the confirmation token dictionary, confirmation token --> registered user */ private final Map<String, RegisteredUser> confirmationTokenDictionary = new HashMap<>(); /** the cached texai@texai.org password */ private String texaiPassword = null; /** the random number generator */ private final Random random = new Random(); /** Constructs a new TestChatSession instance. */ public TestChatSession() { } /** Determines whether the given username is available. * * @param username the user name * @param httpRequest the HTTP request * @param channel the channel */ @Override public void determineUsernameAvailability( final String username, final HttpRequest httpRequest, final Channel channel) { //Preconditions assert username != null : "username must not be null"; assert !username.isEmpty() : "username must not be empty"; assert httpRequest != null : "httpRequest must not be null"; assert channel != null : "channel must not be null"; if (IS_DEBUG_LOGGING_ENABLED) { LOGGER.debug("username: " + username); } final String responseString; if (registeredUserDictionaryByUsername.containsKey(username)) { responseString = "false"; } else { responseString = "true"; } NettyHTTPUtils.writeHTMLResponse(httpRequest, responseString, channel, null); } /** Determines whether the given email address is available. * * @param emailAddress the email address * @param httpRequest the HTTP request * @param channel the channel */ @Override public void determineEmailAddressAvailability( final String emailAddress, final HttpRequest httpRequest, final Channel channel) { //Preconditions assert emailAddress != null : "emailAddress must not be null"; assert !emailAddress.isEmpty() : "emailAddress must not be empty"; assert httpRequest != null : "httpRequest must not be null"; assert channel != null : "channel must not be null"; if (IS_DEBUG_LOGGING_ENABLED) { LOGGER.debug("emailAddress: " + emailAddress); } final String responseString; if (registeredUserDictionaryByEmailAddress.containsKey(emailAddress)) { responseString = "false"; } else { responseString = "true"; } NettyHTTPUtils.writeHTMLResponse(httpRequest, responseString, channel, null); } /** Registers the given user. * * @param firstName the first name * @param lastName the last name * @param emailAddress the email address * @param username the user name * @param encryptedPassword the encrypted password * @param httpRequest the HTTP request * @param channel the channel */ @Override public void registerUser( final String firstName, final String lastName, final String emailAddress, final String username, final String encryptedPassword, final HttpRequest httpRequest, final Channel channel) { //Preconditions assert firstName != null : "firstName must not be null"; assert !firstName.isEmpty() : "firstName must not be empty"; assert lastName != null : "lastName must not be null"; assert !lastName.isEmpty() : "lastName must not be empty"; assert emailAddress != null : "emailAddress must not be null"; assert !registeredUserDictionaryByEmailAddress.containsKey(emailAddress); assert !emailAddress.isEmpty() : "emailAddress must not be empty"; assert username != null : "username must not be null"; assert !username.isEmpty() : "username must not be empty"; assert !registeredUserDictionaryByUsername.containsKey(username); assert encryptedPassword != null : "encryptedPassword must not be null"; assert !encryptedPassword.isEmpty() : "encryptedPassword must not be empty"; assert httpRequest != null : "httpRequest must not be null"; assert channel != null : "channel must not be null"; if (IS_DEBUG_LOGGING_ENABLED) { LOGGER.debug("firstName: " + firstName); LOGGER.debug("lastName: " + lastName); LOGGER.debug("emailAddress: " + emailAddress); LOGGER.debug("username: " + username); LOGGER.debug("encryptedPassword: " + encryptedPassword); } final RegisteredUser registeredUser = new TestRegisteredUser( firstName, lastName, emailAddress, username, encryptedPassword); final String confirmationToken = String.valueOf(Math.abs(random.nextLong())); confirmationTokenDictionary.put(confirmationToken, registeredUser); // send confirmation email in both text and html format final String confirmationURL = "http://localhost:8001/registration-confirmation&token=" + confirmationToken; if (IS_DEBUG_LOGGING_ENABLED) { LOGGER.debug("confirmationURL: " + confirmationURL); } final String htmlContent = "<html><body>" + "To complete your Texai.org registration, please visit the following link." + "<p>" + "<a href=\"" + confirmationURL + "\">" + confirmationURL + "</a>" + "<p>" + "Please disregard this message if you did not intend to register for teaching me." + "<p>" + "-Texai" + "</body></html>"; final String textContent = "To complete your Texai.org registration, please visit the following link.\n\n" + confirmationURL + "\n\n" + "Please disregard this message if you did not intend to register for teaching me.\n\n" + "-Texai"; try { sendEmailMessage( emailAddress, "Texai registration confirmation", htmlContent, textContent); } catch (final MessagingException | IOException ex) { LOGGER.error("exception message: " + ex.getMessage()); LOGGER.error("exception: " + ex); NettyHTTPUtils.writeHTMLResponse( httpRequest, "<html><body><h2>An error occured.</h2></body></html>", channel, null); } } /** Confirms the user's email address. * * @param confirmationToken the confirmation token * @param sessionDictionary the session dictionary, parameter --> value * @return whether the email address was confirmed OK */ @SuppressWarnings("unchecked") @Override public boolean confirmEmailAddress( final String confirmationToken, final Map<String, Object> sessionDictionary) { //Preconditions assert confirmationToken != null : "confirmationToken must not be null"; assert !confirmationToken.isEmpty() : "confirmationToken must not be empty"; assert sessionDictionary != null : "sessionDictionary must not be null"; if (IS_DEBUG_LOGGING_ENABLED) { LOGGER.debug("confirmationToken: " + confirmationToken); } final RegisteredUser registeredUser = confirmationTokenDictionary.get(confirmationToken); if (registeredUser == null) { return false; } else { final String username = registeredUser.getUsername(); registeredUserDictionaryByUsername.put(username, registeredUser); registeredUserDictionaryByEmailAddress.put(registeredUser.getEmailAddress(), registeredUser); sessionDictionary.put(ChatServer.USERNAME_SESSION_KEY, username); return true; } } /** Authenticates the username and encrypted password. * * @param username the user ID * @param encryptedPassword the encrypted password * @param sessionDictionary the session dictionary, parameter --> value * @param httpRequest the HTTP request * @param channel the channel */ @SuppressWarnings("unchecked") @Override public void authenticate( final String username, final String encryptedPassword, final Map<String, Object> sessionDictionary, final HttpRequest httpRequest, final Channel channel) { //Preconditions assert username != null : "username must not be null"; assert !username.isEmpty() : "username must not be empty"; assert encryptedPassword != null : "encryptedPassword must not be null"; assert !encryptedPassword.isEmpty() : "encryptedPassword must not be empty"; assert sessionDictionary != null : "sessionDictionary must not be null"; assert httpRequest != null : "httpRequest must not be null"; assert channel != null : "channel must not be null"; final String responseString; final RegisteredUser registeredUser = registeredUserDictionaryByUsername.get(username); if (registeredUser == null) { if (IS_DEBUG_LOGGING_ENABLED) { LOGGER.debug("authenticate: " + username + " failed"); LOGGER.debug("username: " + username + " is not on file"); } responseString = "false"; } else if (registeredUser.getEncryptedPassword().equals(encryptedPassword)) { if (IS_DEBUG_LOGGING_ENABLED) { LOGGER.debug("authenticate: " + username + " OK"); } sessionDictionary.put(ChatServer.USERNAME_SESSION_KEY, registeredUser.getUsername()); responseString = "true"; if (IS_DEBUG_LOGGING_ENABLED) { LOGGER.debug("authenticate: " + username + " failed"); LOGGER.debug("encrypted password: " + encryptedPassword); LOGGER.debug("on file: " + registeredUser.getEncryptedPassword()); } } else { responseString = "false"; } NettyHTTPUtils.writeHTMLResponse( httpRequest, responseString, channel, null); } /** Validates the given email address or username. * * @param emailOrUsername the email address or username * @param sessionDictionary the session dictionary, parameter --> value * @param httpRequest the HTTP request * @param channel the channel */ @SuppressWarnings("unchecked") @Override public void validateEmailOrUsername( final String emailOrUsername, final Map<String, Object> sessionDictionary, final HttpRequest httpRequest, final Channel channel) { //Preconditions assert emailOrUsername != null : "emailOrUsername must not be null"; assert !emailOrUsername.isEmpty() : "emailOrUsername must not be empty"; assert sessionDictionary != null : "sessionDictionary must not be null"; assert httpRequest != null : "httpRequest must not be null"; assert channel != null : "channel must not be null"; final String responseString; RegisteredUser registeredUser = registeredUserDictionaryByUsername.get(emailOrUsername); if (registeredUser == null) { registeredUser = registeredUserDictionaryByEmailAddress.get(emailOrUsername); } if (registeredUser == null) { if (IS_DEBUG_LOGGING_ENABLED) { LOGGER.debug("validateEmailOrUserName: " + emailOrUsername + " failed"); } responseString = "false"; } else { if (IS_DEBUG_LOGGING_ENABLED) { LOGGER.debug("validateEmailOrUserName: " + emailOrUsername + " OK"); } sessionDictionary.put(ChatServer.USERNAME_SESSION_KEY, registeredUser.getUsername()); responseString = "true"; } NettyHTTPUtils.writeHTMLResponse( httpRequest, responseString, channel, null); if (responseString.equals("false")) { return; } // send confirmation email in both text and html format @SuppressWarnings("null") final String username = registeredUser.getUsername(); final String newPassword = OneWayEncryptionService.getInstance().encrypt(random.nextLong()).substring(0, 12); final String plainText = username + newPassword; registeredUser.setEncryptedPassword(OneWayEncryptionService.getInstance().encrypt(plainText)); final String htmlContent = "<html><body>" + registeredUser.getFirstName() + " " + registeredUser.getLastName() + ":" + "<p>" + "As you requested, here is your new Texai login information:" + "<p>" + "Username: <b>" + username + "</b><br>" + "Password: <b>" + newPassword + "</b><br>" + "<p>" + "Thanks for teaching me.<br>" + "-Texai" + "</body></html>"; final String textContent = registeredUser.getFirstName() + " " + registeredUser.getLastName() + ":\n\n" + "As you requested, here is your new Texai login information:\n\n" + "Username: " + username + "\n" + "Password: " + newPassword + "\n\n" + "Thanks for teaching me.\n" + "-Texai"; try { sendEmailMessage( registeredUser.getEmailAddress(), "Texai username and new password", htmlContent, textContent); } catch (final MessagingException | IOException ex) { LOGGER.error(ex.getMessage()); NettyHTTPUtils.writeHTMLResponse( httpRequest, "<html><body><h2>An error occured during email or username validation.</h2></body></html>", channel, null); } } /** Sends an email message. * * @param emailAddress the email address * @param subject the subject * @param htmlContent the HTML content * @param textContent the text content * @throws MessagingException when a mail messaging error occurs * @throws IOException when an input/output error occurs */ private void sendEmailMessage( final String emailAddress, final String subject, final String htmlContent, final String textContent) throws MessagingException, IOException { //Preconditions assert emailAddress != null : "emailAddress must not be null"; assert !emailAddress.isEmpty() : "emailAddress must not be empty"; assert subject != null : "subject must not be null"; assert !subject.isEmpty() : "subject must not be empty"; assert htmlContent != null : "htmlContent must not be null"; assert !htmlContent.isEmpty() : "htmlContent must not be empty"; assert textContent != null : "textContent must not be null"; assert !textContent.isEmpty() : "textContent must not be empty"; // send confirmation email in both text and html format final Properties props = System.getProperties(); final String smtpServer = "mail.texai.org"; props.put("mail.smtp.host", smtpServer); final javax.mail.Session mailSession = javax.mail.Session.getDefaultInstance(props, null); //mailSession.setDebug(true); mailSession.getProperties().put("mail.smtp.auth", "true"); final Message message = new MimeMessage(mailSession); message.setFrom(new InternetAddress("texai@texai.org")); message.setRecipients( Message.RecipientType.TO, InternetAddress.parse(emailAddress, false)); message.setSubject(subject); final MimeMultipart mimeMultipart = new MimeMultipart("alternative"); final MimeBodyPart textMimeBodyPart = new MimeBodyPart(); final MimeBodyPart htmlMimeBodyPart = new MimeBodyPart(); textMimeBodyPart.setText(textContent); textMimeBodyPart.setHeader("MIME-Version", "1.0"); textMimeBodyPart.setHeader("Content-Type", "text/plain"); htmlMimeBodyPart.setContent(htmlContent, "text/html"); htmlMimeBodyPart.setHeader("MIME-Version", "1.0"); htmlMimeBodyPart.setHeader("Content-Type", "text/html"); mimeMultipart.addBodyPart(textMimeBodyPart); mimeMultipart.addBodyPart(htmlMimeBodyPart); message.setContent(mimeMultipart); message.setHeader("MIME-Version", "1.0"); message.setHeader("Content-Type", mimeMultipart.getContentType()); message.setSentDate(new Date()); final Transport transport = mailSession.getTransport("smtp"); transport.connect(smtpServer, 26, "texai+texai.org", getTexaiPassword()); transport.sendMessage(message, message.getAllRecipients()); transport.close(); if (IS_DEBUG_LOGGING_ENABLED) { LOGGER.debug("sent email to: " + emailAddress + " subject: " + subject); } } /** Gets the texai@texai.org password. * * @return the texai@texai.org password * @throws IOException when an input/output error occurs */ private String getTexaiPassword() throws IOException { if (texaiPassword == null) { try (BufferedReader bufferedReader = new BufferedReader(new FileReader(System.getProperty("user.home") + "/jabber-client.txt"))) { bufferedReader.readLine(); texaiPassword = bufferedReader.readLine(); } } return texaiPassword; } /** Receives the webcam image message from the web client. * * @param httpRequest the HTTP request * @param channel the channel * @param sessionDictionary the session dictionary, parameter --> value */ @Override public void receiveWebcamImage( final HttpRequest httpRequest, final Channel channel, Map<String, Object> sessionDictionary) { throw new UnsupportedOperationException("Not supported yet."); } /** Receives the web socket text from the web client. * * @param webSocketText the web socket text * @param channel the channel * @param sessionDictionary the session dictionary, parameter --> value */ @Override public void receiveWebSocketText( final String webSocketText, final Channel channel, final Map<String, Object> sessionDictionary) { throw new UnsupportedOperationException("Not supported yet."); } }