From ddf686135b91a9831352f4f9436fcb5447939664 Mon Sep 17 00:00:00 2001 From: Thomas Krause Date: Thu, 3 Sep 2015 18:58:03 +0200 Subject: [PATCH] refactoring the AnnisUser class to fix #434 AnnisUser was not serializable. In case the session was written to the disk by the server AnnisUser could either have transient variables (like the Client) which are invalid after the object has been restored or you can re-create the client on request but then you need to store the password in the session disk storage as well. While creating the "Client" object on the fly and caching it is now implemented, the "password" field is still marked as "transient" to ensure it is not written to disk. The actual new behavior is that the user is properly informed whenever the password was lost because it was written to disk and he/she is requested to login again instead of silently failing in this case. --- .../main/java/annis/gui/LoginListener.java | 2 - .../src/main/java/annis/gui/MainToolbar.java | 51 +++++++++++++++++ .../LoginServletRequestHandler.java | 14 +++-- .../main/java/annis/libgui/AnnisBaseUI.java | 27 +++++---- .../src/main/java/annis/libgui/AnnisUser.java | 55 ++++++++++--------- .../src/main/java/annis/libgui/Helper.java | 31 ++++++++++- .../annis/libgui/LoginDataLostException.java | 28 ++++++++++ 7 files changed, 162 insertions(+), 46 deletions(-) create mode 100644 annis-libgui/src/main/java/annis/libgui/LoginDataLostException.java diff --git a/annis-gui/src/main/java/annis/gui/LoginListener.java b/annis-gui/src/main/java/annis/gui/LoginListener.java index b6b1f2170f..d58f1f4db7 100644 --- a/annis-gui/src/main/java/annis/gui/LoginListener.java +++ b/annis-gui/src/main/java/annis/gui/LoginListener.java @@ -15,8 +15,6 @@ */ package annis.gui; -import com.sun.jersey.api.client.WebResource; - /** * * @author Thomas Krause diff --git a/annis-gui/src/main/java/annis/gui/MainToolbar.java b/annis-gui/src/main/java/annis/gui/MainToolbar.java index 07db3393ae..f68f2fc1e7 100644 --- a/annis-gui/src/main/java/annis/gui/MainToolbar.java +++ b/annis-gui/src/main/java/annis/gui/MainToolbar.java @@ -24,6 +24,8 @@ import static annis.libgui.AnnisBaseUI.USER_LOGIN_ERROR; import annis.libgui.AnnisUser; import annis.libgui.Helper; +import annis.libgui.LoginDataLostException; +import com.google.common.eventbus.Subscribe; import com.vaadin.data.validator.EmailValidator; import com.vaadin.server.DeploymentConfiguration; import com.vaadin.server.ExternalResource; @@ -313,6 +315,32 @@ public void buttonClick(Button.ClickEvent event) updateSidebarState(); MainToolbar.this.updateUserInformation(); } + + @Override + public void attach() + { + super.attach(); + + UI ui = UI.getCurrent(); + if(ui instanceof AnnisBaseUI) + { + ((AnnisBaseUI) ui).getLoginDataLostBus().register(this); + } + } + + @Override + public void detach() + { + UI ui = UI.getCurrent(); + if(ui instanceof AnnisBaseUI) + { + ((AnnisBaseUI) ui).getLoginDataLostBus().unregister(this); + } + + super.detach(); + } + + @@ -579,5 +607,28 @@ public void call(JSONArray arguments) throws JSONException } } + + @Subscribe + public void handleLoginDataLostException(LoginDataLostException ex) + { + + Notification.show("Login data was lost, please login again.", + "Due to a server misconfiguration the login-data was lost. Please contact the adminstrator of this ANNIS instance.", + Notification.Type.WARNING_MESSAGE); + + for (LoginListener l : loginListeners) + { + try + { + l.onLogout(); + } + catch (Exception loginEx) + { + log.error("exception thrown while notifying login listeners", loginEx); + } + } + updateUserInformation(); + + } } diff --git a/annis-gui/src/main/java/annis/gui/requesthandler/LoginServletRequestHandler.java b/annis-gui/src/main/java/annis/gui/requesthandler/LoginServletRequestHandler.java index 867bb15990..8859e671a7 100644 --- a/annis-gui/src/main/java/annis/gui/requesthandler/LoginServletRequestHandler.java +++ b/annis-gui/src/main/java/annis/gui/requesthandler/LoginServletRequestHandler.java @@ -18,6 +18,7 @@ import annis.libgui.AnnisBaseUI; import annis.libgui.AnnisUser; import annis.libgui.Helper; +import annis.libgui.LoginDataLostException; import com.google.common.base.Charsets; import com.google.common.io.CharStreams; import com.google.common.io.Resources; @@ -145,16 +146,15 @@ private void doPost(VaadinSession session, VaadinRequest request, String webserviceURL = (String) annisServiceURLObject; - Client client = Helper.createRESTClient(username, password); - try { - WebResource res = client.resource(webserviceURL) + AnnisUser user = new AnnisUser(username, password); + WebResource res = user.getClient().resource(webserviceURL) .path("admin").path("is-authenticated"); if ("true".equalsIgnoreCase(res.get(String.class))) { // everything ok, save this user configuration for re-use - Helper.setUser(new AnnisUser(username, client)); + Helper.setUser(user); } } catch (ClientHandlerException ex) @@ -163,6 +163,12 @@ private void doPost(VaadinSession session, VaadinRequest request, "Authentification error: " + ex.getMessage()); response.setStatus(502); // bad gateway } + catch (LoginDataLostException ex) + { + session.getSession().setAttribute(AnnisBaseUI.USER_LOGIN_ERROR, + "Lost password in memory. Sorry."); + response.setStatus(500); // server error + } catch (UniformInterfaceException ex) { if (ex.getResponse().getStatus() == Response.Status.UNAUTHORIZED. diff --git a/annis-libgui/src/main/java/annis/libgui/AnnisBaseUI.java b/annis-libgui/src/main/java/annis/libgui/AnnisBaseUI.java index 3221bbe8e0..214f8f5ccc 100644 --- a/annis-libgui/src/main/java/annis/libgui/AnnisBaseUI.java +++ b/annis-libgui/src/main/java/annis/libgui/AnnisBaseUI.java @@ -22,9 +22,9 @@ import ch.qos.logback.classic.joran.JoranConfigurator; import ch.qos.logback.core.joran.spi.JoranException; import com.google.common.base.Charsets; +import com.google.common.eventbus.EventBus; import com.google.common.hash.Hashing; import com.google.common.io.Files; -import com.sun.jersey.api.client.Client; import com.vaadin.annotations.Theme; import com.vaadin.sass.internal.ScssStylesheet; import com.vaadin.server.ClassResource; @@ -36,9 +36,6 @@ import com.vaadin.server.VaadinSession; import com.vaadin.ui.UI; import java.io.*; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; import java.util.Collection; import java.util.Collections; import java.util.Date; @@ -102,6 +99,8 @@ public class AnnisBaseUI extends UI implements PluginSystem, Serializable private TreeSet alreadyAddedCSS = new TreeSet(); + private final EventBus loginDataLostBus = new EventBus(); + @Override protected void init(VaadinRequest request) { @@ -112,9 +111,9 @@ protected void init(VaadinRequest request) // store the webservice URL property explicitly in the session in order to // access it from the "external" servlets - getSession().getSession().setAttribute(WEBSERVICEURL_KEY, - getSession().getAttribute(Helper.KEY_WEB_SERVICE_URL)); - + getSession().getSession().setAttribute(WEBSERVICEURL_KEY, + getSession().getAttribute(Helper.KEY_WEB_SERVICE_URL)); + getSession().setAttribute(CONTEXT_PATH, request.getContextPath()); alreadyAddedCSS.clear(); @@ -384,9 +383,7 @@ private void checkIfRemoteLoggedIn(VaadinRequest request) String remoteUser = request.getRemoteUser(); if(remoteUser != null) { - // treat as anonymous user - Client client = Helper.createRESTClient();; - Helper.setUser(new AnnisUser(remoteUser, client, true)); + Helper.setUser(new AnnisUser(remoteUser, null, true)); } } @@ -503,7 +500,13 @@ public boolean handleRequest(VaadinSession session, VaadinRequest request, checkIfRemoteLoggedIn(request); // we never write any information in this handler return false; - } - + } } + + public EventBus getLoginDataLostBus() + { + return loginDataLostBus; + } + + } \ No newline at end of file diff --git a/annis-libgui/src/main/java/annis/libgui/AnnisUser.java b/annis-libgui/src/main/java/annis/libgui/AnnisUser.java index 846d3e9215..fb62dac72e 100644 --- a/annis-libgui/src/main/java/annis/libgui/AnnisUser.java +++ b/annis-libgui/src/main/java/annis/libgui/AnnisUser.java @@ -16,24 +16,29 @@ package annis.libgui; import com.sun.jersey.api.client.Client; +import java.io.Serializable; +import java.util.concurrent.atomic.AtomicInteger; -public class AnnisUser +public class AnnisUser implements Serializable { private transient Client client; - private String userName = ""; - private boolean remote = false; - - public AnnisUser(String userName, Client client) + private final String userName; + /** Never store the password on the disk */ + private final transient String password; + private final boolean remote; + + public AnnisUser(String userName, String password) { this.userName = userName; - this.client = client; + this.password = password; + this.remote = false; } - public AnnisUser(String userName, Client client, boolean remote) + public AnnisUser(String userName, String password, boolean remote) { this.userName = userName; - this.client = client; + this.password = password; this.remote = remote; } @@ -43,20 +48,28 @@ public String getUserName() return userName; } - public void setUserName(String userName) - { - this.userName = userName; - } - public Client getClient() + public Client getClient() throws LoginDataLostException { + if(client == null) + { + if(remote == true) + { + // treat as anonymous user + client = Helper.createRESTClient(); + } + else + { + if(password == null) + { + throw new LoginDataLostException(); + } + client = Helper.createRESTClient(userName, password); + } + } return client; } - public void setClient(Client client) - { - this.client = client; - } /** * True if the user a remote user, thus cannot e.g. logout by itself @@ -66,12 +79,4 @@ public boolean isRemote() { return remote; } - - public void setRemote(boolean remote) - { - this.remote = remote; - } - - - } diff --git a/annis-libgui/src/main/java/annis/libgui/Helper.java b/annis-libgui/src/main/java/annis/libgui/Helper.java index 7efe772d00..fed0c4ce7b 100644 --- a/annis-libgui/src/main/java/annis/libgui/Helper.java +++ b/annis-libgui/src/main/java/annis/libgui/Helper.java @@ -31,7 +31,6 @@ import com.sun.jersey.api.client.GenericType; import com.sun.jersey.api.client.UniformInterfaceException; import com.sun.jersey.api.client.WebResource; -import com.sun.jersey.api.uri.UriComponent; import com.sun.jersey.client.apache4.ApacheHttpClient4; import com.sun.jersey.client.apache4.config.ApacheHttpClient4Config; import com.sun.jersey.client.apache4.config.DefaultApacheHttpClient4Config; @@ -208,7 +207,20 @@ public static WebResource getAnnisWebResource(String uri, AnnisUser user) if (user != null) { - return user.getClient().resource(uri); + try + { + return user.getClient().resource(uri); + } + catch (LoginDataLostException ex) + { + log.error("Could not restore the login-data from session, user will invalidated", ex); + setUser(null); + UI ui = UI.getCurrent(); + if(ui instanceof AnnisBaseUI) + { + ((AnnisBaseUI) ui).getLoginDataLostBus().post(ex); + } + } } // use the anonymous client @@ -234,7 +246,20 @@ public static AsyncWebResource getAnnisAsyncWebResource(String uri, if (user != null) { - return user.getClient().asyncResource(uri); + try + { + return user.getClient().asyncResource(uri); + } + catch (LoginDataLostException ex) + { + log.error("Could not restore the login-data from session, user will invalidated", ex); + setUser(null); + UI ui = UI.getCurrent(); + if(ui instanceof AnnisBaseUI) + { + ((AnnisBaseUI) ui).getLoginDataLostBus().post(ex); + } + } } // use the anonymous client diff --git a/annis-libgui/src/main/java/annis/libgui/LoginDataLostException.java b/annis-libgui/src/main/java/annis/libgui/LoginDataLostException.java new file mode 100644 index 0000000000..1552d8ed14 --- /dev/null +++ b/annis-libgui/src/main/java/annis/libgui/LoginDataLostException.java @@ -0,0 +1,28 @@ +/* + * Copyright 2015 Corpuslinguistic working group Humboldt University Berlin. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package annis.libgui; + +/** + * This indicates the login-data is not available any longer. + * + * If this exception was thrown the user interface should show + * to the user that he/she is effectivly logged out. + * @author Thomas Krause + */ +public class LoginDataLostException extends Exception +{ + +}