Skip to content
This repository has been archived by the owner on Feb 12, 2023. It is now read-only.

Commit

Permalink
Avoid wrong Location when redirecting to other pages (#393)
Browse files Browse the repository at this point in the history
- When Aggregate is running behind a proxy, Swing will generate wrong Location URLs (wrong domain, wrong port number, or even removing the port number)
- Also, make the multimode and the change password redirect urls static and absolute to leave no space for Swing messing up stuff (the tradeoff is coupling the multimode login servlet to the actual location of the redirected pages, which is not desirable under normal circumstances)
  • Loading branch information
ggalmazor committed Jan 29, 2019
1 parent 8441f70 commit fd13004
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 125 deletions.
28 changes: 28 additions & 0 deletions 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);
}
}
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
}
}
Expand Down
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
}
}
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}

Expand All @@ -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)) {
Expand Down
@@ -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;
}
}

0 comments on commit fd13004

Please sign in to comment.