From 6597429e38541a7851d85d793cd6f412390a7710 Mon Sep 17 00:00:00 2001 From: Vincentyfication Date: Wed, 21 Jan 2015 22:47:02 +0800 Subject: [PATCH 1/4] Added OpenID login solution for #33. Additionally, fixes string concatenation in a loop used as a parameter, which comes with undesired side effect. --- .../chat/StackExchangeChat.java | 121 ++++++++++++++++-- 1 file changed, 110 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/gmail/inverseconduit/chat/StackExchangeChat.java b/src/main/java/com/gmail/inverseconduit/chat/StackExchangeChat.java index c7b2a77..192eb73 100644 --- a/src/main/java/com/gmail/inverseconduit/chat/StackExchangeChat.java +++ b/src/main/java/com/gmail/inverseconduit/chat/StackExchangeChat.java @@ -1,5 +1,11 @@ package com.gmail.inverseconduit.chat; +import org.w3c.dom.Document; +import org.xml.sax.SAXException; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; import java.net.URL; import java.util.*; @@ -7,14 +13,30 @@ import java.util.concurrent.TimeUnit; import java.util.logging.Logger; -import com.gargoylesoftware.htmlunit.*; +import com.gargoylesoftware.htmlunit.BrowserVersion; +import com.gargoylesoftware.htmlunit.ElementNotFoundException; +import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException; +import com.gargoylesoftware.htmlunit.HttpMethod; +import com.gargoylesoftware.htmlunit.WebClient; +import com.gargoylesoftware.htmlunit.WebRequest; +import com.gargoylesoftware.htmlunit.WebResponse; import com.gargoylesoftware.htmlunit.html.HtmlForm; import com.gargoylesoftware.htmlunit.html.HtmlPage; import com.gargoylesoftware.htmlunit.util.NameValuePair; +import com.gargoylesoftware.htmlunit.util.UrlUtils; import com.gargoylesoftware.htmlunit.util.WebConnectionWrapper; import com.gmail.inverseconduit.SESite; -import com.gmail.inverseconduit.datatype.*; +import com.gmail.inverseconduit.datatype.ChatDescriptor; +import com.gmail.inverseconduit.datatype.ChatEventType; +import com.gmail.inverseconduit.datatype.ChatMessage; +import com.gmail.inverseconduit.datatype.CredentialsProvider; +import com.gmail.inverseconduit.datatype.JSONChatEvents; +import com.gmail.inverseconduit.datatype.ProviderDescriptor; +import com.gmail.inverseconduit.datatype.SeChatDescriptor; import com.gmail.inverseconduit.utils.PrintUtils; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.gson.Gson; public class StackExchangeChat implements ChatInterface { @@ -52,19 +74,96 @@ public StackExchangeChat() { */ @Override public boolean login(ProviderDescriptor descriptor, CredentialsProvider credentials) { - HtmlPage loginPage = getLoginPage(descriptor); - if (null == loginPage) { return false; } + if (descriptor.getDescription().toString().matches("(?!.*?(?:stackoverflow|meta)).+")) + loggedIn = openIdLogin(credentials.getIdentificator(), credentials.getAuthenticator()); + else { + HtmlPage loginPage = getLoginPage(descriptor); + if (null == loginPage) { return false; } - HtmlForm loginForm = processLoginPage(credentials.getIdentificator(), credentials.getAuthenticator(), loginPage); + HtmlForm loginForm = processLoginPage(credentials.getIdentificator(), credentials.getAuthenticator(), loginPage); - WebResponse response = submitLoginForm(loginForm); - if (null == response) { return false; } + WebResponse response = submitLoginForm(loginForm); + if (null == response) { return false; } - loggedIn = (response.getStatusCode() == 200); - logLoginMessage(descriptor.getDescription().toString(), credentials.getIdentificator(), response); + loggedIn = (response.getStatusCode() == 200); + logLoginMessage(descriptor.getDescription().toString(), credentials.getIdentificator(), response); + } return loggedIn; } + public boolean openIdLogin(String email, String password) { + try { + final DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + { // Log into Stack Exchange. + WebResponse getRes = webClient.loadWebResponse(new WebRequest( + UrlUtils.toUrlUnsafe("https://openid.stackexchange.com/account/login"), + HttpMethod.GET + )); + if (getRes == null) + // throw new IllegalArgumentException("Could not get OpenID fkey."); + return false; + String response = getRes.getContentAsString(); + LOGGER.info("Got response from fetching fkey of OpenID login:" + response); + + WebRequest post = new WebRequest( + UrlUtils.toUrlUnsafe("https://openid.stackexchange.com/account/login/submit"), + HttpMethod.POST + ); + try { + post.setRequestParameters(ImmutableList.of( + new NameValuePair("email", email), + new NameValuePair("password", password), + new NameValuePair("fkey", + builder.parse(getRes.getContentAsStream()) + .getElementById("fkey") + .getAttribute("value") + ) + )); + } catch(SAXException die) { + // throw new IllegalStateException("Parsing response text as DOM has caused a parse exception.", die); + return false; + } + WebResponse postRes = webClient.loadWebResponse(post); + if (null == postRes || !Strings.isNullOrEmpty(postRes.getResponseHeaderValue("p3p"))) + // throw new IllegalArgumentException("Could not post to submit of OpenID."); + return false; + } + + { // Log into Stack Exchange Chat. + WebResponse getRes = webClient.loadWebResponse(new WebRequest( + UrlUtils.toUrlUnsafe("http://stackexchange.com/users/chat-login"), + HttpMethod.GET + )); + WebRequest post = new WebRequest( + UrlUtils.toUrlUnsafe("http://chat.stackexchange.com/users/login/global"), + HttpMethod.POST + ); + try { + Document dom = builder.parse(getRes.getContentAsStream()); + post.setRequestParameters(ImmutableList.of( + new NameValuePair("authToken", dom.getElementById("authToken").getAttribute("value")), + new NameValuePair("nonce", dom.getElementById("nonce").getAttribute("value")) + )); + } catch(SAXException die) { + // throw new IllegalStateException("Parsing response text as DOM has caused a parse exception.", die); + return false; + } + post.setAdditionalHeaders(ImmutableMap.of( + "Origin", "http://chat.stackexchange.com", + "Referer", "http://chat.stackexchange.com" + )); + WebResponse postRes = webClient.loadWebResponse(post); + String response = postRes.getContentAsString(); + return response.contains("Welcome"); + } + } catch (ParserConfigurationException e) { + // Ignore - virtually impossible. + } catch(IOException e) { + e.printStackTrace(); + } + return false; + } + private WebResponse submitLoginForm(HtmlForm loginForm) { WebResponse response; try { @@ -180,7 +279,7 @@ private String handleMessageOversize(final SeChatDescriptor descriptor, String m StringBuilder messageBuilder = new StringBuilder(); for (String token : messageTokens) { if (messageBuilder.length() + token.length() < 495) { - messageBuilder.append(" " + token); + messageBuilder.append(' ').append(token); } else { LOGGER.info("Message split part: " + messageBuilder.toString()); @@ -227,7 +326,7 @@ private boolean sendMessage(String restRootUrl, String fkey, String message) { * Queries the 5 latest messages for all chatrooms and enqueues them to * the subscribed {@link ChatWorker Workers}, respecting the already handled * timestamps as maintained internally. - * + * * @see ChatInterface#queryMessages() */ @Override From c8980adc04592e237c4a0687d7efdcec294d1de1 Mon Sep 17 00:00:00 2001 From: Vogel612 Date: Sat, 4 Apr 2015 14:43:14 +0200 Subject: [PATCH 2/4] Fixed overlooked merge conflict in javadocs --- .../java/com/gmail/inverseconduit/chat/StackExchangeChat.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/com/gmail/inverseconduit/chat/StackExchangeChat.java b/src/main/java/com/gmail/inverseconduit/chat/StackExchangeChat.java index 2107b3f..0ea964a 100644 --- a/src/main/java/com/gmail/inverseconduit/chat/StackExchangeChat.java +++ b/src/main/java/com/gmail/inverseconduit/chat/StackExchangeChat.java @@ -294,13 +294,11 @@ private boolean sendMessage(String restRootUrl, String fkey, String message) { } /** - * <<<<<<< HEAD * Queries the 5 latest messages for all chatrooms and enqueues them to * the subscribed {@link ChatWorker Workers}, respecting the already handled * timestamps as maintained internally. * * @see ChatInterface#queryMessages() - * ======= {@inheritDoc} >>>>>>> master */ @Override public void broadcast(final String message) { From b044de43c97cb50b18d7aa392186156faa524ff3 Mon Sep 17 00:00:00 2001 From: Vogel612 Date: Sat, 4 Apr 2015 15:59:44 +0200 Subject: [PATCH 3/4] Added clean logging to Stackexchange Login --- .../com/gmail/inverseconduit/bot/Program.java | 4 ++-- .../inverseconduit/chat/StackExchangeChat.java | 17 ++++++++++------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/gmail/inverseconduit/bot/Program.java b/src/main/java/com/gmail/inverseconduit/bot/Program.java index c05045d..16cbd95 100644 --- a/src/main/java/com/gmail/inverseconduit/bot/Program.java +++ b/src/main/java/com/gmail/inverseconduit/bot/Program.java @@ -47,7 +47,7 @@ public Program(ChatInterface chatInterface) throws IOException { bots.add(DefaultBot.create(chatInterface)); bots.add(new InteractionBot(chatInterface)); bots.forEach(chatInterface::subscribe); - LOGGER.log(Level.FINE, "Basic component setup complete"); + LOGGER.log(Level.INFO, "Basic component setup complete"); } /** @@ -56,7 +56,7 @@ public Program(ChatInterface chatInterface) throws IOException { * Additionally all bots that were created on startup are started. */ public void startup() { - LOGGER.log(Level.FINER, "Beginning startup process"); + LOGGER.log(Level.INFO, "Beginning startup process"); for (Integer room : config.getRooms()) { // FIXME: isn't always Stackoverflow chatInterface.joinChat(new SeChatDescriptor.DescriptorBuilder(SESite.STACK_OVERFLOW).setRoom(() -> room).build()); diff --git a/src/main/java/com/gmail/inverseconduit/chat/StackExchangeChat.java b/src/main/java/com/gmail/inverseconduit/chat/StackExchangeChat.java index 0ea964a..9ee6677 100644 --- a/src/main/java/com/gmail/inverseconduit/chat/StackExchangeChat.java +++ b/src/main/java/com/gmail/inverseconduit/chat/StackExchangeChat.java @@ -92,9 +92,11 @@ public boolean openIdLogin(String email, String password) { final DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); { // Log into Stack Exchange. WebResponse getRes = webClient.loadWebResponse(new WebRequest(UrlUtils.toUrlUnsafe("https://openid.stackexchange.com/account/login"), HttpMethod.GET)); - if (getRes == null) - // throw new IllegalArgumentException("Could not get OpenID fkey."); + if (getRes == null) { + LOGGER.log(Level.SEVERE, "Could not get OpenID fkey."); return false; + } + String response = getRes.getContentAsString(); LOGGER.info("Got response from fetching fkey of OpenID login:" + response); @@ -103,13 +105,14 @@ public boolean openIdLogin(String email, String password) { post.setRequestParameters(ImmutableList.of(new NameValuePair("email", email), new NameValuePair("password", password), new NameValuePair("fkey", builder.parse(getRes.getContentAsStream()).getElementById("fkey").getAttribute("value")))); } catch(SAXException die) { - // throw new IllegalStateException("Parsing response text as DOM has caused a parse exception.", die); + LOGGER.log(Level.SEVERE, "Parsing response text as DOM has caused a parse exception.", die); return false; } WebResponse postRes = webClient.loadWebResponse(post); - if (null == postRes || !Strings.isNullOrEmpty(postRes.getResponseHeaderValue("p3p"))) - // throw new IllegalArgumentException("Could not post to submit of OpenID."); + if (null == postRes || !Strings.isNullOrEmpty(postRes.getResponseHeaderValue("p3p"))) { + LOGGER.log(Level.SEVERE, "Could not post to submit of OpenID."); return false; + } } { // Log into Stack Exchange Chat. @@ -120,7 +123,7 @@ public boolean openIdLogin(String email, String password) { post.setRequestParameters(ImmutableList.of(new NameValuePair("authToken", dom.getElementById("authToken").getAttribute("value")), new NameValuePair("nonce", dom.getElementById("nonce").getAttribute("value")))); } catch(SAXException die) { - // throw new IllegalStateException("Parsing response text as DOM has caused a parse exception.", die); + LOGGER.log(Level.SEVERE, "Parsing response text as DOM has caused a parse exception.", die); return false; } post.setAdditionalHeaders(ImmutableMap.of("Origin", "http://chat.stackexchange.com", "Referer", "http://chat.stackexchange.com")); @@ -131,7 +134,7 @@ public boolean openIdLogin(String email, String password) { } catch(ParserConfigurationException e) { // Ignore - virtually impossible. } catch(IOException e) { - e.printStackTrace(); + LOGGER.log(Level.SEVERE, "Encountered IO-Exception when trying to login: ", e); } return false; } From bc6106200098928ca6e0da780c098532853430b6 Mon Sep 17 00:00:00 2001 From: Vogel612 Date: Sat, 4 Apr 2015 16:00:08 +0200 Subject: [PATCH 4/4] fixed double replies when calling eval-command --- .../java/com/gmail/inverseconduit/scripts/ScriptRunner.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/gmail/inverseconduit/scripts/ScriptRunner.java b/src/main/java/com/gmail/inverseconduit/scripts/ScriptRunner.java index 840ffb1..3314a99 100644 --- a/src/main/java/com/gmail/inverseconduit/scripts/ScriptRunner.java +++ b/src/main/java/com/gmail/inverseconduit/scripts/ScriptRunner.java @@ -55,8 +55,8 @@ public String evaluateGroovy(ChatMessage msg, String commandText) { } LOGGER.info("Result:" + result); return result == null - ? String.format(":%d [tag:groovy]: no result", msg.getMessageId()) - : String.format(":%d [tag:groovy]: %s", msg.getMessageId(), result.toString()); + ? "[tag:groovy]: no result" + : String.format("[tag:groovy]: %s", result.toString()); } public void evaluateAndCache(String commandText) {