Send authentication header only for /login, ensure redirectUri is never empty.
diff --git a/src/main/java/com/thesamet/gerrit/plugins/saml/AuthenticatedUser.java b/src/main/java/com/thesamet/gerrit/plugins/saml/AuthenticatedUser.java
index 432e36f..75fbff9 100644
--- a/src/main/java/com/thesamet/gerrit/plugins/saml/AuthenticatedUser.java
+++ b/src/main/java/com/thesamet/gerrit/plugins/saml/AuthenticatedUser.java
@@ -6,11 +6,11 @@
private String email;
private String externalId;
- public AuthenticatedUser(String username, String displayName, String email, String extenalId) {
+ public AuthenticatedUser(String username, String displayName, String email, String externalId) {
this.username = username;
this.displayName = displayName;
this.email = email;
- this.externalId = extenalId;
+ this.externalId = externalId;
}
public String getUsername() {
diff --git a/src/main/java/com/thesamet/gerrit/plugins/saml/SamlWebFilter.java b/src/main/java/com/thesamet/gerrit/plugins/saml/SamlWebFilter.java
index b093e06..6731571 100644
--- a/src/main/java/com/thesamet/gerrit/plugins/saml/SamlWebFilter.java
+++ b/src/main/java/com/thesamet/gerrit/plugins/saml/SamlWebFilter.java
@@ -32,6 +32,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import javax.annotation.Nonnull;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
@@ -45,6 +46,7 @@
@Singleton
class SamlWebFilter implements Filter {
static final String GERRIT_LOGOUT = "/logout";
+ static final String GERRIT_LOGIN = "/login";
static final String SAML_POSTBACK = "/plugins/gerrit-saml-plugin/saml";
private static final String SESSION_ATTR_USER = "Gerrit-Saml-User";
@@ -126,8 +128,7 @@
"saml/" + user.getId()));
String redirectUri = context.getRequest().getParameter("RelayState");
- log.debug("Got {}", redirectUri);
- if (null == redirectUri) {
+ if (null == redirectUri || redirectUri.isEmpty()) {
redirectUri = "/";
}
context.getResponse().sendRedirect(redirectUri);
@@ -143,9 +144,13 @@
}
@Override
- public void doFilter(ServletRequest request, ServletResponse response,
+ public void doFilter(ServletRequest incomingRequest, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
- HttpServletRequest httpRequest = (HttpServletRequest) request;
+ /* The first thing we do is to wrap the request in an anonymous request, so in case
+ a malicious user is trying to set the headers manually, they'll be discarded.
+ */
+ HttpServletRequest httpRequest = new AnonymousHttpRequest(
+ (HttpServletRequest) incomingRequest);
HttpServletResponse httpResponse = (HttpServletResponse) response;
AuthenticatedUser user = userFromRequest(httpRequest);
@@ -153,33 +158,24 @@
if (isSamlPostback(httpRequest)) {
J2EContext context = new J2EContext(httpRequest, httpResponse);
signin(context);
+ } else if (isGerritLogin(httpRequest)) {
+ if (user == null) {
+ J2EContext context = new J2EContext(httpRequest, httpResponse);
+ redirectToIdentityProvider(context);
+ } else {
+ HttpServletRequest req = new AuthenticatedHttpRequest(httpRequest, user);
+ chain.doFilter(req, response);
+ }
} else if (isGerritLogout(httpRequest)) {
signout(httpRequest, httpResponse);
- } else if (isAllowedWithoutAuth(httpRequest)) {
- // We allow URLs to continue without a user (and hence without the authentication
- // headers. It is up for Gerrit to approve or deny these requests. We do it
- // specifically for favicon.ico: it could be that during a normal authentication
- // redirect, the browser will try to fetch /favicon.ico which will start a
- // parallel authentication process, but it will override the redirectUri and the
- // user will be redirected to /favicon.ico. This would have been eliminated
- // if pac4j would allow obtaining RelayState...
- chain.doFilter(httpRequest, response);
- } else if (user == null) {
- J2EContext context = new J2EContext(httpRequest, httpResponse);
- redirectToIdentityProvider(context);
} else {
- HttpServletRequest req = new AuthenticatedHttpRequest(httpRequest, user);
- chain.doFilter(req, response);
+ chain.doFilter(httpRequest, httpResponse);
}
} catch (final RequiresHttpAction requiresHttpAction) {
throw new TechnicalException("Unexpected HTTP action", requiresHttpAction);
}
}
- private boolean isAllowedWithoutAuth(HttpServletRequest httpRequest) {
- return (httpRequest.getRequestURI().equals("/favicon.ico"));
- }
-
private void redirectToIdentityProvider(J2EContext context)
throws RequiresHttpAction {
String redirectUri = Url.decode(context
@@ -192,6 +188,10 @@
saml2Client.redirect(context, true);
}
+ private static boolean isGerritLogin(HttpServletRequest request) {
+ return request.getRequestURI().indexOf(GERRIT_LOGIN) >= 0;
+ }
+
private static boolean isGerritLogout(HttpServletRequest request) {
return request.getRequestURI().indexOf(GERRIT_LOGOUT) >= 0;
}
@@ -244,7 +244,7 @@
private AuthenticatedUser user;
public AuthenticatedHttpRequest(HttpServletRequest request,
- AuthenticatedUser user) {
+ @Nonnull AuthenticatedUser user) {
super(request);
this.user = user;
}
@@ -275,4 +275,34 @@
}
}
}
+
+ private class AnonymousHttpRequest extends HttpServletRequestWrapper {
+ public AnonymousHttpRequest(HttpServletRequest request) {
+ super(request);
+ }
+
+ @Override
+ public Enumeration<String> getHeaderNames() {
+ final Enumeration<String> wrappedHeaderNames = super.getHeaderNames();
+
+ HashSet<String> headerNames = new HashSet<>();
+ while (wrappedHeaderNames.hasMoreElements()) {
+ String header = wrappedHeaderNames.nextElement();
+ if (!authHeaders.contains(header.toUpperCase())) {
+ headerNames.add(wrappedHeaderNames.nextElement());
+ }
+ }
+ return Iterators.asEnumeration(headerNames.iterator());
+ }
+
+ @Override
+ public String getHeader(String name) {
+ String nameUpperCase = name.toUpperCase();
+ if (authHeaders.contains(nameUpperCase)) {
+ return null;
+ } else {
+ return super.getHeader(name);
+ }
+ }
+ }
}