diff --git a/src/main/java/org/opendatakit/aggregate/HttpUtils.java b/src/main/java/org/opendatakit/aggregate/HttpUtils.java new file mode 100644 index 0000000000..d8963c8e11 --- /dev/null +++ b/src/main/java/org/opendatakit/aggregate/HttpUtils.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2019 Nafundi + * + * 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 org.opendatakit.aggregate; + +import javax.servlet.http.HttpServletResponse; + +public class HttpUtils { + public static void redirect(HttpServletResponse resp, String url) { + // Can't use resp.sendRedirect() because it messes up + // the domain and ports when Aggregate is running behind a proxy + resp.setStatus(302); + resp.setHeader("Location", url); + } +} diff --git a/src/main/java/org/opendatakit/aggregate/servlet/AggregateHtmlServlet.java b/src/main/java/org/opendatakit/aggregate/servlet/AggregateHtmlServlet.java index 09b835e794..3e3c9693c0 100644 --- a/src/main/java/org/opendatakit/aggregate/servlet/AggregateHtmlServlet.java +++ b/src/main/java/org/opendatakit/aggregate/servlet/AggregateHtmlServlet.java @@ -22,6 +22,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.opendatakit.aggregate.ContextFactory; +import org.opendatakit.aggregate.HttpUtils; import org.opendatakit.aggregate.constants.common.UIConsts; import org.opendatakit.aggregate.server.ServerPreferencesProperties; import org.opendatakit.common.persistence.Datastore; @@ -105,7 +106,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws if (!url.getHost().equalsIgnoreCase(req.getServerName())) { // we should redirect over to the proper fully-formed URL. logger.info("Incoming servername: " + req.getServerName() + " expected: " + url.getHost() + " -- redirecting."); - resp.sendRedirect(newUrl); + HttpUtils.redirect(resp, newUrl); return; } @@ -139,7 +140,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws if (directToConfigTab) { newUrl += "#admin/permission///"; logger.info("Redirect to configuration tab: " + newUrl); - resp.sendRedirect(newUrl); + HttpUtils.redirect(resp, newUrl); return; } } diff --git a/src/main/java/org/opendatakit/aggregate/servlet/ClearSessionThenLoginServlet.java b/src/main/java/org/opendatakit/aggregate/servlet/ClearSessionThenLoginServlet.java index c204d3b2b1..f51b10ff4d 100644 --- a/src/main/java/org/opendatakit/aggregate/servlet/ClearSessionThenLoginServlet.java +++ b/src/main/java/org/opendatakit/aggregate/servlet/ClearSessionThenLoginServlet.java @@ -20,6 +20,7 @@ import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.opendatakit.aggregate.ContextFactory; +import org.opendatakit.aggregate.HttpUtils; import org.opendatakit.common.security.UserService; import org.opendatakit.common.web.CallingContext; @@ -62,17 +63,17 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws String newUrl; if (isAnon) { // anonymous user -- go to the login page... - newUrl = cc.getWebApplicationURL("multimode_login.html"); + newUrl = "/multimode_login.html"; } else { // we are logged in via token-based or basic or digest auth. // redirect to Spring's logout url... - newUrl = cc.getWebApplicationURL(cc.getUserService().createLogoutURL()); + newUrl = "/" + cc.getUserService().createLogoutURL(); } // preserve the query string (helps with GWT debugging) String query = req.getQueryString(); if (query != null && query.length() != 0) { newUrl += "?" + query; } - resp.sendRedirect(newUrl); + HttpUtils.redirect(resp, newUrl); } } diff --git a/src/main/java/org/opendatakit/aggregate/servlet/MultimodeLoginPageServlet.java b/src/main/java/org/opendatakit/aggregate/servlet/MultimodeLoginPageServlet.java index 26bc5c9e2d..094e28da22 100644 --- a/src/main/java/org/opendatakit/aggregate/servlet/MultimodeLoginPageServlet.java +++ b/src/main/java/org/opendatakit/aggregate/servlet/MultimodeLoginPageServlet.java @@ -24,6 +24,7 @@ import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.opendatakit.aggregate.ContextFactory; +import org.opendatakit.aggregate.HttpUtils; import org.opendatakit.common.web.CallingContext; import org.opendatakit.common.web.constants.BasicConsts; import org.opendatakit.common.web.constants.HtmlConsts; @@ -71,7 +72,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws e.printStackTrace(); } // go to the proper page (we'll most likely be redirected back to here for authentication) - resp.sendRedirect(newUrl); + HttpUtils.redirect(resp, newUrl); return; } @@ -89,7 +90,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws } // check for XSS attacks. The redirect string is emitted within single and double - // quotes. It is a URL with :, /, ? and # characters. But it should not contain + // quotes. It is a URL with :, /, ? and # characters. But it should not contain // quotes, parentheses or semicolons. String cleanString = redirectParamString.replaceAll(BAD_PARAMETER_CHARACTERS, ""); if (!cleanString.equals(redirectParamString)) { diff --git a/src/main/java/org/opendatakit/common/security/server/SecurityServiceImpl.java b/src/main/java/org/opendatakit/common/security/server/SecurityServiceImpl.java index 86c8334b0b..cb3a442b95 100644 --- a/src/main/java/org/opendatakit/common/security/server/SecurityServiceImpl.java +++ b/src/main/java/org/opendatakit/common/security/server/SecurityServiceImpl.java @@ -1,118 +1,117 @@ -/* - * Copyright (C) 2011 University of Washington - * - * 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 org.opendatakit.common.security.server; - -import com.google.gwt.user.server.rpc.RemoteServiceServlet; -import javax.servlet.http.HttpServletRequest; -import org.opendatakit.aggregate.ContextFactory; -import org.opendatakit.aggregate.servlet.UserManagePasswordsServlet; -import org.opendatakit.common.persistence.Datastore; -import org.opendatakit.common.persistence.client.exception.DatastoreFailureException; -import org.opendatakit.common.persistence.exception.ODKDatastoreException; -import org.opendatakit.common.security.SecurityBeanDefs; -import org.opendatakit.common.security.User; -import org.opendatakit.common.security.client.RealmSecurityInfo; -import org.opendatakit.common.security.client.UserSecurityInfo; -import org.opendatakit.common.security.client.exception.AccessDeniedException; -import org.opendatakit.common.security.common.GrantedAuthorityName; -import org.opendatakit.common.security.spring.RegisteredUsersTable; -import org.opendatakit.common.web.CallingContext; -import org.opendatakit.common.web.constants.BasicConsts; -import org.springframework.security.authentication.encoding.MessageDigestPasswordEncoder; - -/** - * GWT Server implementation for the SecurityService interface. This provides - * privileges context to the client and is therefore accessible to anyone with a - * ROLE_USER privilege. - * - * @author mitchellsundt@gmail.com - */ -public class SecurityServiceImpl extends RemoteServiceServlet implements - org.opendatakit.common.security.client.security.SecurityService { - - /** - * - */ - private static final long serialVersionUID = -7360632450727200941L; - - @Override - public UserSecurityInfo getUserInfo() throws DatastoreFailureException { - - HttpServletRequest req = this.getThreadLocalRequest(); - CallingContext cc = ContextFactory.getCallingContext(this, req); - - Datastore ds = cc.getDatastore(); - User user = cc.getCurrentUser(); - - String uriUser = user.getUriUser(); - UserSecurityInfo info; - try { - if (user.isRegistered()) { - RegisteredUsersTable t; - t = RegisteredUsersTable.getUserByUri(uriUser, ds, user); - if (t != null) { - info = new UserSecurityInfo(t.getUsername(), t.getFullName(), t.getEmail(), - UserSecurityInfo.UserType.REGISTERED); - SecurityServiceUtil.setAuthenticationLists(info, t.getUri(), cc); - } else { - throw new DatastoreFailureException("Unable to retrieve user record"); - } - } else if (user.isAnonymous()) { - info = new UserSecurityInfo(User.ANONYMOUS_USER, User.ANONYMOUS_USER_NICKNAME, null, - UserSecurityInfo.UserType.ANONYMOUS); - SecurityServiceUtil.setAuthenticationListsForSpecialUser(info, - GrantedAuthorityName.USER_IS_ANONYMOUS, cc); - } else { - // should never get to this case via interactive actions... - throw new DatastoreFailureException("Internal error: 45443"); - } - } catch (ODKDatastoreException e) { - e.printStackTrace(); - throw new DatastoreFailureException(e); - } - return info; - } - - @Override - public RealmSecurityInfo getRealmInfo(String xsrfString) throws AccessDeniedException, DatastoreFailureException { - - HttpServletRequest req = this.getThreadLocalRequest(); - CallingContext cc = ContextFactory.getCallingContext(this, req); - - if (!req.getSession().getId().equals(xsrfString)) { - throw new AccessDeniedException("Invalid request"); - } - - RealmSecurityInfo r = new RealmSecurityInfo(); - r.setRealmString(cc.getUserService().getCurrentRealm().getRealmString()); - MessageDigestPasswordEncoder mde = (MessageDigestPasswordEncoder) cc - .getBean(SecurityBeanDefs.BASIC_AUTH_PASSWORD_ENCODER); - r.setBasicAuthHashEncoding(mde.getAlgorithm()); - r.setSuperUserEmail(cc.getUserService().getSuperUserEmail()); - r.setSuperUsername(cc.getUserService().getSuperUserUsername()); - try { - r.setSuperUsernamePasswordSet(cc.getUserService().isSuperUsernamePasswordSet(cc)); - } catch (ODKDatastoreException e) { - e.printStackTrace(); - throw new DatastoreFailureException("Unable to access datastore"); - } - // User interface layer uses this URL to submit password changes securely - r.setChangeUserPasswordURL(cc.getSecureServerURL() + BasicConsts.FORWARDSLASH - + UserManagePasswordsServlet.ADDR); - return r; - } -} +/* + * Copyright (C) 2011 University of Washington + * + * 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 org.opendatakit.common.security.server; + +import com.google.gwt.user.server.rpc.RemoteServiceServlet; +import javax.servlet.http.HttpServletRequest; +import org.opendatakit.aggregate.ContextFactory; +import org.opendatakit.aggregate.servlet.UserManagePasswordsServlet; +import org.opendatakit.common.persistence.Datastore; +import org.opendatakit.common.persistence.client.exception.DatastoreFailureException; +import org.opendatakit.common.persistence.exception.ODKDatastoreException; +import org.opendatakit.common.security.SecurityBeanDefs; +import org.opendatakit.common.security.User; +import org.opendatakit.common.security.client.RealmSecurityInfo; +import org.opendatakit.common.security.client.UserSecurityInfo; +import org.opendatakit.common.security.client.exception.AccessDeniedException; +import org.opendatakit.common.security.common.GrantedAuthorityName; +import org.opendatakit.common.security.spring.RegisteredUsersTable; +import org.opendatakit.common.web.CallingContext; +import org.opendatakit.common.web.constants.BasicConsts; +import org.springframework.security.authentication.encoding.MessageDigestPasswordEncoder; + +/** + * GWT Server implementation for the SecurityService interface. This provides + * privileges context to the client and is therefore accessible to anyone with a + * ROLE_USER privilege. + * + * @author mitchellsundt@gmail.com + */ +public class SecurityServiceImpl extends RemoteServiceServlet implements + org.opendatakit.common.security.client.security.SecurityService { + + /** + * + */ + private static final long serialVersionUID = -7360632450727200941L; + + @Override + public UserSecurityInfo getUserInfo() throws DatastoreFailureException { + + HttpServletRequest req = this.getThreadLocalRequest(); + CallingContext cc = ContextFactory.getCallingContext(this, req); + + Datastore ds = cc.getDatastore(); + User user = cc.getCurrentUser(); + + String uriUser = user.getUriUser(); + UserSecurityInfo info; + try { + if (user.isRegistered()) { + RegisteredUsersTable t; + t = RegisteredUsersTable.getUserByUri(uriUser, ds, user); + if (t != null) { + info = new UserSecurityInfo(t.getUsername(), t.getFullName(), t.getEmail(), + UserSecurityInfo.UserType.REGISTERED); + SecurityServiceUtil.setAuthenticationLists(info, t.getUri(), cc); + } else { + throw new DatastoreFailureException("Unable to retrieve user record"); + } + } else if (user.isAnonymous()) { + info = new UserSecurityInfo(User.ANONYMOUS_USER, User.ANONYMOUS_USER_NICKNAME, null, + UserSecurityInfo.UserType.ANONYMOUS); + SecurityServiceUtil.setAuthenticationListsForSpecialUser(info, + GrantedAuthorityName.USER_IS_ANONYMOUS, cc); + } else { + // should never get to this case via interactive actions... + throw new DatastoreFailureException("Internal error: 45443"); + } + } catch (ODKDatastoreException e) { + e.printStackTrace(); + throw new DatastoreFailureException(e); + } + return info; + } + + @Override + public RealmSecurityInfo getRealmInfo(String xsrfString) throws AccessDeniedException, DatastoreFailureException { + + HttpServletRequest req = this.getThreadLocalRequest(); + CallingContext cc = ContextFactory.getCallingContext(this, req); + + if (!req.getSession().getId().equals(xsrfString)) { + throw new AccessDeniedException("Invalid request"); + } + + RealmSecurityInfo r = new RealmSecurityInfo(); + r.setRealmString(cc.getUserService().getCurrentRealm().getRealmString()); + MessageDigestPasswordEncoder mde = (MessageDigestPasswordEncoder) cc + .getBean(SecurityBeanDefs.BASIC_AUTH_PASSWORD_ENCODER); + r.setBasicAuthHashEncoding(mde.getAlgorithm()); + r.setSuperUserEmail(cc.getUserService().getSuperUserEmail()); + r.setSuperUsername(cc.getUserService().getSuperUserUsername()); + try { + r.setSuperUsernamePasswordSet(cc.getUserService().isSuperUsernamePasswordSet(cc)); + } catch (ODKDatastoreException e) { + e.printStackTrace(); + throw new DatastoreFailureException("Unable to access datastore"); + } + // User interface layer uses this URL to submit password changes securely + r.setChangeUserPasswordURL("/" + UserManagePasswordsServlet.ADDR); + return r; + } +}