Skip to content

Commit

Permalink
SONAR-8416 add event log on error in OAuth2 and Base authent
Browse files Browse the repository at this point in the history
  • Loading branch information
sns-seb committed Dec 1, 2016
1 parent 99edb79 commit ec5d3d8
Show file tree
Hide file tree
Showing 20 changed files with 339 additions and 173 deletions.
Expand Up @@ -23,14 +23,14 @@
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.sonar.api.server.authentication.UnauthorizedException;
import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers; import org.sonar.api.utils.log.Loggers;
import org.sonar.server.authentication.event.AuthenticationException;


import static java.lang.String.format; import static java.lang.String.format;
import static java.net.URLEncoder.encode; import static java.net.URLEncoder.encode;


public class AuthenticationError { final class AuthenticationError {


private static final String UNAUTHORIZED_PATH = "/sessions/unauthorized"; private static final String UNAUTHORIZED_PATH = "/sessions/unauthorized";
private static final String UNAUTHORIZED_PATH_WITH_MESSAGE = UNAUTHORIZED_PATH + "?message=%s"; private static final String UNAUTHORIZED_PATH_WITH_MESSAGE = UNAUTHORIZED_PATH + "?message=%s";
Expand All @@ -40,22 +40,26 @@ private AuthenticationError() {
// Utility class // Utility class
} }


public static void handleError(Exception e, HttpServletResponse response, String message) { static void handleError(Exception e, HttpServletResponse response, String message) {
LOGGER.error(message, e); LOGGER.error(message, e);
redirectToUnauthorized(response); redirectToUnauthorized(response);
} }


public static void handleError(HttpServletResponse response, String message) { static void handleError(HttpServletResponse response, String message) {
LOGGER.error(message); LOGGER.error(message);
redirectToUnauthorized(response); redirectToUnauthorized(response);
} }


public static void handleUnauthorizedError(UnauthorizedException e, HttpServletResponse response) { static void handleAuthenticationError(AuthenticationException e, HttpServletResponse response) {
redirectTo(response, getPath(e)); redirectTo(response, getPath(e));
} }


private static String getPath(UnauthorizedException e) { private static String getPath(AuthenticationException e) {
return format(UNAUTHORIZED_PATH_WITH_MESSAGE, encodeMessage(e.getMessage())); String publicMessage = e.getPublicMessage();
if (publicMessage == null || publicMessage.isEmpty()) {
return UNAUTHORIZED_PATH;
}
return format(UNAUTHORIZED_PATH_WITH_MESSAGE, encodeMessage(publicMessage));
} }


private static String encodeMessage(String message) { private static String encodeMessage(String message) {
Expand All @@ -66,7 +70,7 @@ private static String encodeMessage(String message) {
} }
} }


private static void redirectToUnauthorized(HttpServletResponse response) { public static void redirectToUnauthorized(HttpServletResponse response) {
redirectTo(response, UNAUTHORIZED_PATH); redirectTo(response, UNAUTHORIZED_PATH);
} }


Expand Down
Expand Up @@ -20,6 +20,7 @@
package org.sonar.server.authentication; package org.sonar.server.authentication;


import java.io.IOException; import java.io.IOException;
import javax.annotation.CheckForNull;
import javax.servlet.FilterChain; import javax.servlet.FilterChain;
import javax.servlet.FilterConfig; import javax.servlet.FilterConfig;
import javax.servlet.ServletException; import javax.servlet.ServletException;
Expand All @@ -33,11 +34,14 @@
import org.sonar.api.server.authentication.OAuth2IdentityProvider; import org.sonar.api.server.authentication.OAuth2IdentityProvider;
import org.sonar.api.server.authentication.UnauthorizedException; import org.sonar.api.server.authentication.UnauthorizedException;
import org.sonar.api.web.ServletFilter; import org.sonar.api.web.ServletFilter;
import org.sonar.server.authentication.event.AuthenticationEvent;
import org.sonar.server.authentication.event.AuthenticationException;


import static com.google.common.base.Strings.isNullOrEmpty; import static com.google.common.base.Strings.isNullOrEmpty;
import static java.lang.String.format; import static java.lang.String.format;
import static org.sonar.server.authentication.AuthenticationError.handleAuthenticationError;
import static org.sonar.server.authentication.AuthenticationError.handleError; import static org.sonar.server.authentication.AuthenticationError.handleError;
import static org.sonar.server.authentication.AuthenticationError.handleUnauthorizedError; import static org.sonar.server.authentication.event.AuthenticationEvent.Source;


public class InitFilter extends ServletFilter { public class InitFilter extends ServletFilter {


Expand All @@ -47,12 +51,15 @@ public class InitFilter extends ServletFilter {
private final BaseContextFactory baseContextFactory; private final BaseContextFactory baseContextFactory;
private final OAuth2ContextFactory oAuth2ContextFactory; private final OAuth2ContextFactory oAuth2ContextFactory;
private final Server server; private final Server server;
private final AuthenticationEvent authenticationEvent;


public InitFilter(IdentityProviderRepository identityProviderRepository, BaseContextFactory baseContextFactory, OAuth2ContextFactory oAuth2ContextFactory, Server server) { public InitFilter(IdentityProviderRepository identityProviderRepository, BaseContextFactory baseContextFactory,
OAuth2ContextFactory oAuth2ContextFactory, Server server, AuthenticationEvent authenticationEvent) {
this.identityProviderRepository = identityProviderRepository; this.identityProviderRepository = identityProviderRepository;
this.baseContextFactory = baseContextFactory; this.baseContextFactory = baseContextFactory;
this.oAuth2ContextFactory = oAuth2ContextFactory; this.oAuth2ContextFactory = oAuth2ContextFactory;
this.server = server; this.server = server;
this.authenticationEvent = authenticationEvent;
} }


@Override @Override
Expand All @@ -63,35 +70,80 @@ public UrlPattern doGetPattern() {
@Override @Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletRequest httpRequest = (HttpServletRequest) request;
String requestURI = httpRequest.getRequestURI(); HttpServletResponse httpResponse = (HttpServletResponse) response;
String keyProvider = "";
IdentityProvider provider = resolveProviderOrHandleResponse(httpRequest, httpResponse);
if (provider != null) {
handleProvider(httpRequest, httpResponse, provider);
}
}

@CheckForNull
private IdentityProvider resolveProviderOrHandleResponse(HttpServletRequest request, HttpServletResponse response) {
String requestURI = request.getRequestURI();
String providerKey = extractKeyProvider(requestURI, server.getContextPath() + INIT_CONTEXT);
if (providerKey == null) {
handleError(response, "No provider key found in URI");
return null;
}
try { try {
keyProvider = extractKeyProvider(requestURI, server.getContextPath() + INIT_CONTEXT); return identityProviderRepository.getEnabledByKey(providerKey);
IdentityProvider provider = identityProviderRepository.getEnabledByKey(keyProvider);
if (provider instanceof BaseIdentityProvider) {
BaseIdentityProvider baseIdentityProvider = (BaseIdentityProvider) provider;
baseIdentityProvider.init(baseContextFactory.newContext(httpRequest, (HttpServletResponse) response, baseIdentityProvider));
} else if (provider instanceof OAuth2IdentityProvider) {
OAuth2IdentityProvider oAuth2IdentityProvider = (OAuth2IdentityProvider) provider;
oAuth2IdentityProvider.init(oAuth2ContextFactory.newContext(httpRequest, (HttpServletResponse) response, oAuth2IdentityProvider));
} else {
throw new UnsupportedOperationException(format("Unsupported IdentityProvider class: %s ", provider.getClass()));
}
} catch (UnauthorizedException e) {
handleUnauthorizedError(e, (HttpServletResponse) response);
} catch (Exception e) { } catch (Exception e) {
handleError(e, (HttpServletResponse) response, format("Fail to initialize authentication with provider '%s'", keyProvider)); handleError(e, response, format("Failed to retrieve IdentityProvider for key '%s'", providerKey));
return null;
} }
} }


public static String extractKeyProvider(String requestUri, String context) { @CheckForNull
private static String extractKeyProvider(String requestUri, String context) {
if (requestUri.contains(context)) { if (requestUri.contains(context)) {
String key = requestUri.replace(context, ""); String key = requestUri.replace(context, "");
if (!isNullOrEmpty(key)) { if (!isNullOrEmpty(key)) {
return key; return key;
} }
} }
throw new IllegalArgumentException("A valid identity provider key is required."); return null;
}

private void handleProvider(HttpServletRequest request, HttpServletResponse response, IdentityProvider provider) {
try {
if (provider instanceof BaseIdentityProvider) {
handleBaseIdentityProvider(request, response, (BaseIdentityProvider) provider);
} else if (provider instanceof OAuth2IdentityProvider) {
handleOAuth2IdentityProvider(request, response, (OAuth2IdentityProvider) provider);
} else {
handleError(response, format("Unsupported IdentityProvider class: %s", provider.getClass()));
}
} catch (AuthenticationException e) {
authenticationEvent.failure(request, e);
handleAuthenticationError(e, response);
} catch (Exception e) {
handleError(e, response, format("Fail to initialize authentication with provider '%s'", provider.getKey()));
}
}

private void handleBaseIdentityProvider(HttpServletRequest request, HttpServletResponse response, BaseIdentityProvider provider) {
try {
provider.init(baseContextFactory.newContext(request, response, provider));
} catch (UnauthorizedException e) {
throw AuthenticationException.newBuilder()
.setSource(Source.external(provider))
.setMessage(e.getMessage())
.setPublicMessage(e.getMessage())
.build();
}
}

private void handleOAuth2IdentityProvider(HttpServletRequest request, HttpServletResponse response, OAuth2IdentityProvider provider) {
try {
provider.init(oAuth2ContextFactory.newContext(request, response, provider));
} catch (UnauthorizedException e) {
throw AuthenticationException.newBuilder()
.setSource(Source.oauth2(provider))
.setMessage(e.getMessage())
.setPublicMessage(e.getMessage())
.build();
}
} }


@Override @Override
Expand Down
Expand Up @@ -28,19 +28,19 @@
import javax.servlet.ServletResponse; import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.sonar.api.CoreProperties;
import org.sonar.api.platform.Server; import org.sonar.api.platform.Server;
import org.sonar.api.server.authentication.IdentityProvider; import org.sonar.api.server.authentication.IdentityProvider;
import org.sonar.api.server.authentication.OAuth2IdentityProvider; import org.sonar.api.server.authentication.OAuth2IdentityProvider;
import org.sonar.api.server.authentication.UnauthorizedException; import org.sonar.api.server.authentication.UnauthorizedException;
import org.sonar.api.server.authentication.UserIdentity; import org.sonar.api.server.authentication.UserIdentity;
import org.sonar.api.web.ServletFilter; import org.sonar.api.web.ServletFilter;
import org.sonar.server.authentication.event.AuthenticationEvent; import org.sonar.server.authentication.event.AuthenticationEvent;
import org.sonar.server.authentication.event.AuthenticationException;


import static com.google.common.base.Strings.isNullOrEmpty; import static com.google.common.base.Strings.isNullOrEmpty;
import static java.lang.String.format; import static java.lang.String.format;
import static org.sonar.server.authentication.AuthenticationError.handleAuthenticationError;
import static org.sonar.server.authentication.AuthenticationError.handleError; import static org.sonar.server.authentication.AuthenticationError.handleError;
import static org.sonar.server.authentication.AuthenticationError.handleUnauthorizedError;
import static org.sonar.server.authentication.event.AuthenticationEvent.Source; import static org.sonar.server.authentication.event.AuthenticationEvent.Source;


public class OAuth2CallbackFilter extends ServletFilter { public class OAuth2CallbackFilter extends ServletFilter {
Expand Down Expand Up @@ -68,37 +68,75 @@ public UrlPattern doGetPattern() {
@Override @Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletRequest httpRequest = (HttpServletRequest) request;
String requestUri = httpRequest.getRequestURI(); HttpServletResponse httpResponse = (HttpServletResponse) response;
String keyProvider = "";
IdentityProvider provider = resolveProviderOrHandleResponse(httpRequest, httpResponse);
if (provider != null) {
handleProvider(httpRequest, (HttpServletResponse) response, provider);
}
}

@CheckForNull
private IdentityProvider resolveProviderOrHandleResponse(HttpServletRequest request, HttpServletResponse response) {
String requestUri = request.getRequestURI();
String providerKey = extractKeyProvider(requestUri, server.getContextPath() + CALLBACK_PATH);
if (providerKey == null) {
handleError(response, "No provider key found in URI");
return null;
}
try { try {
keyProvider = extractKeyProvider(requestUri, server.getContextPath() + CALLBACK_PATH); return identityProviderRepository.getEnabledByKey(providerKey);
IdentityProvider provider = identityProviderRepository.getEnabledByKey(keyProvider);
if (provider instanceof OAuth2IdentityProvider) {
OAuth2IdentityProvider oauthProvider = (OAuth2IdentityProvider) provider;
WrappedContext context = new WrappedContext(oAuth2ContextFactory.newCallback(httpRequest, (HttpServletResponse) response, oauthProvider));
oauthProvider.callback(context);
if (context.isAuthenticated()) {
authenticationEvent.login(httpRequest, context.getLogin(), Source.oauth2(oauthProvider));
}
} else {
handleError((HttpServletResponse) response, format("Not an OAuth2IdentityProvider: %s", provider.getClass()));
}
} catch (UnauthorizedException e) {
handleUnauthorizedError(e, (HttpServletResponse) response);
} catch (Exception e) { } catch (Exception e) {
handleError(e, (HttpServletResponse) response, handleError(e, response, format("Failed to retrieve IdentityProvider for key '%s'", providerKey));
keyProvider.isEmpty() ? "Fail to callback authentication" : format("Fail to callback authentication with '%s'", keyProvider)); return null;
} }
} }


@CheckForNull
private static String extractKeyProvider(String requestUri, String context) { private static String extractKeyProvider(String requestUri, String context) {
if (requestUri.contains(context)) { if (requestUri.contains(context)) {
String key = requestUri.replace(context, ""); String key = requestUri.replace(context, "");
if (!isNullOrEmpty(key)) { if (!isNullOrEmpty(key)) {
return key; return key;
} }
} }
throw new IllegalArgumentException(String.format("A valid identity provider key is required. Please check that property '%s' is valid.", CoreProperties.SERVER_BASE_URL)); return null;
}

private void handleProvider(HttpServletRequest request, HttpServletResponse response, IdentityProvider provider) {
try {
if (provider instanceof OAuth2IdentityProvider) {
handleOAuth2Provider(response, request, (OAuth2IdentityProvider) provider);
} else {
handleError(response, format("Not an OAuth2IdentityProvider: %s", provider.getClass()));
}
} catch (AuthenticationException e) {
authenticationEvent.failure(request, e);
handleAuthenticationError(e, response);
} catch (Exception e) {
handleError(e, response, format("Fail to callback authentication with '%s'", provider.getKey()));
}
}

private void handleOAuth2Provider(HttpServletResponse response, HttpServletRequest httpRequest, OAuth2IdentityProvider oAuth2Provider) {
OAuth2CallbackFilter.WrappedContext context = new OAuth2CallbackFilter.WrappedContext(oAuth2ContextFactory.newCallback(httpRequest, response, oAuth2Provider));
try {
oAuth2Provider.callback(context);
} catch (UnauthorizedException e) {
throw AuthenticationException.newBuilder()
.setSource(Source.oauth2(oAuth2Provider))
.setMessage(e.getMessage())
.setPublicMessage(e.getMessage())
.build();
}
if (context.isAuthenticated()) {
authenticationEvent.login(httpRequest, context.getLogin(), Source.oauth2(oAuth2Provider));
} else {
throw AuthenticationException.newBuilder()
.setSource(Source.oauth2(oAuth2Provider))
.setMessage("Plugin did not call authenticate")
.build();
}
} }


@Override @Override
Expand Down
Expand Up @@ -36,13 +36,13 @@
import org.sonar.api.config.Settings; import org.sonar.api.config.Settings;
import org.sonar.api.server.authentication.Display; import org.sonar.api.server.authentication.Display;
import org.sonar.api.server.authentication.IdentityProvider; import org.sonar.api.server.authentication.IdentityProvider;
import org.sonar.api.server.authentication.UnauthorizedException;
import org.sonar.api.server.authentication.UserIdentity; import org.sonar.api.server.authentication.UserIdentity;
import org.sonar.api.utils.System2; import org.sonar.api.utils.System2;
import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers; import org.sonar.api.utils.log.Loggers;
import org.sonar.db.user.UserDto; import org.sonar.db.user.UserDto;
import org.sonar.server.authentication.event.AuthenticationEvent; import org.sonar.server.authentication.event.AuthenticationEvent;
import org.sonar.server.authentication.event.AuthenticationException;
import org.sonar.server.exceptions.BadRequestException; import org.sonar.server.exceptions.BadRequestException;


import static org.apache.commons.lang.StringUtils.defaultIfBlank; import static org.apache.commons.lang.StringUtils.defaultIfBlank;
Expand Down Expand Up @@ -119,7 +119,10 @@ public Optional<UserDto> authenticate(HttpServletRequest request, HttpServletRes
try { try {
return doAuthenticate(request, response); return doAuthenticate(request, response);
} catch (BadRequestException e) { } catch (BadRequestException e) {
throw new UnauthorizedException(e.getMessage(), e); throw AuthenticationException.newBuilder()
.setSource(Source.sso())
.setMessage(e.getMessage())
.build();
} }
} }


Expand Down
Expand Up @@ -24,6 +24,7 @@
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import org.sonar.api.server.authentication.BaseIdentityProvider; import org.sonar.api.server.authentication.BaseIdentityProvider;
import org.sonar.api.server.authentication.IdentityProvider;
import org.sonar.api.server.authentication.OAuth2IdentityProvider; import org.sonar.api.server.authentication.OAuth2IdentityProvider;


import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
Expand Down Expand Up @@ -127,7 +128,7 @@ public static Source jwt() {
return JWT_INSTANCE; return JWT_INSTANCE;
} }


public static Source external(BaseIdentityProvider identityProvider) { public static Source external(IdentityProvider identityProvider) {
return new Source( return new Source(
Method.EXTERNAL, Provider.EXTERNAL, Method.EXTERNAL, Provider.EXTERNAL,
requireNonNull(identityProvider, "identityProvider can't be null").getName()); requireNonNull(identityProvider, "identityProvider can't be null").getName());
Expand Down

0 comments on commit ec5d3d8

Please sign in to comment.