Skip to content

Commit

Permalink
SONAR-7763 Allow authentication using basic HTTP authentication in Java
Browse files Browse the repository at this point in the history
  • Loading branch information
julienlancelot committed Jun 29, 2016
1 parent cd8f9c0 commit d82358c
Show file tree
Hide file tree
Showing 19 changed files with 750 additions and 367 deletions.
Expand Up @@ -21,6 +21,7 @@


import org.sonar.core.platform.Module; import org.sonar.core.platform.Module;
import org.sonar.server.authentication.ws.AuthenticationWs; import org.sonar.server.authentication.ws.AuthenticationWs;
import org.sonar.server.authentication.ws.LoginAction;


public class AuthenticationModule extends Module { public class AuthenticationModule extends Module {
@Override @Override
Expand All @@ -34,12 +35,13 @@ protected void configureModule() {
OAuth2ContextFactory.class, OAuth2ContextFactory.class,
UserIdentityAuthenticator.class, UserIdentityAuthenticator.class,
OAuthCsrfVerifier.class, OAuthCsrfVerifier.class,
ValidateJwtTokenFilter.class, UserSessionInitializer.class,
JwtSerializer.class, JwtSerializer.class,
JwtHttpHandler.class, JwtHttpHandler.class,
JwtCsrfVerifier.class, JwtCsrfVerifier.class,
AuthLoginAction.class, LoginAction.class,
CredentialsAuthenticator.class, CredentialsAuthenticator.class,
RealmAuthenticator.class); RealmAuthenticator.class,
BasicAuthenticator.class);
} }
} }
Expand Up @@ -24,18 +24,26 @@
import org.sonar.api.platform.Server; import org.sonar.api.platform.Server;
import org.sonar.api.server.authentication.BaseIdentityProvider; import org.sonar.api.server.authentication.BaseIdentityProvider;
import org.sonar.api.server.authentication.UserIdentity; import org.sonar.api.server.authentication.UserIdentity;
import org.sonar.db.DbClient;
import org.sonar.db.user.UserDto; import org.sonar.db.user.UserDto;
import org.sonar.server.user.ServerUserSession;
import org.sonar.server.user.ThreadLocalUserSession;


public class BaseContextFactory { public class BaseContextFactory {


private final DbClient dbClient;
private final ThreadLocalUserSession threadLocalUserSession;
private final UserIdentityAuthenticator userIdentityAuthenticator; private final UserIdentityAuthenticator userIdentityAuthenticator;
private final Server server; private final Server server;
private final JwtHttpHandler jwtHttpHandler; private final JwtHttpHandler jwtHttpHandler;


public BaseContextFactory(UserIdentityAuthenticator userIdentityAuthenticator, Server server, JwtHttpHandler jwtHttpHandler) { public BaseContextFactory(DbClient dbClient, UserIdentityAuthenticator userIdentityAuthenticator, Server server, JwtHttpHandler jwtHttpHandler,
ThreadLocalUserSession threadLocalUserSession) {
this.dbClient = dbClient;
this.userIdentityAuthenticator = userIdentityAuthenticator; this.userIdentityAuthenticator = userIdentityAuthenticator;
this.server = server; this.server = server;
this.jwtHttpHandler = jwtHttpHandler; this.jwtHttpHandler = jwtHttpHandler;
this.threadLocalUserSession = threadLocalUserSession;
} }


public BaseIdentityProvider.Context newContext(HttpServletRequest request, HttpServletResponse response, BaseIdentityProvider identityProvider) { public BaseIdentityProvider.Context newContext(HttpServletRequest request, HttpServletResponse response, BaseIdentityProvider identityProvider) {
Expand Down Expand Up @@ -72,6 +80,7 @@ public String getServerBaseURL() {
public void authenticate(UserIdentity userIdentity) { public void authenticate(UserIdentity userIdentity) {
UserDto userDto = userIdentityAuthenticator.authenticate(userIdentity, identityProvider); UserDto userDto = userIdentityAuthenticator.authenticate(userIdentity, identityProvider);
jwtHttpHandler.generateToken(userDto, response); jwtHttpHandler.generateToken(userDto, response);
threadLocalUserSession.set(ServerUserSession.createForUser(dbClient, userDto));
} }
} }
} }
@@ -0,0 +1,104 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

package org.sonar.server.authentication;

import static java.util.Locale.ENGLISH;
import static org.elasticsearch.common.Strings.isEmpty;

import com.google.common.base.Charsets;
import java.util.Base64;
import java.util.Optional;
import javax.servlet.http.HttpServletRequest;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.user.UserDto;
import org.sonar.server.exceptions.UnauthorizedException;
import org.sonar.server.usertoken.UserTokenAuthenticator;

public class BasicAuthenticator {

private static final Base64.Decoder BASE64_DECODER = Base64.getDecoder();

private static final String AUTHORIZATION_HEADER = "Authorization";
private static final String BASIC_AUTHORIZATION = "BASIC";

private final DbClient dbClient;
private final CredentialsAuthenticator credentialsAuthenticator;
private final UserTokenAuthenticator userTokenAuthenticator;

public BasicAuthenticator(DbClient dbClient, CredentialsAuthenticator credentialsAuthenticator,
UserTokenAuthenticator userTokenAuthenticator) {
this.dbClient = dbClient;
this.credentialsAuthenticator = credentialsAuthenticator;
this.userTokenAuthenticator = userTokenAuthenticator;
}

public Optional<UserDto> authenticate(HttpServletRequest request) {
String authorizationHeader = request.getHeader(AUTHORIZATION_HEADER);
if (authorizationHeader == null || !authorizationHeader.toUpperCase(ENGLISH).startsWith(BASIC_AUTHORIZATION)) {
return Optional.empty();
}

String[] credentials = getCredentials(authorizationHeader);
String login = credentials[0];
String password = credentials[1];
return Optional.of(authenticate(login, password, request));
}

private static String[] getCredentials(String authorizationHeader) {
String basicAuthEncoded = authorizationHeader.substring(6);
String basicAuthDecoded = new String(BASE64_DECODER.decode(basicAuthEncoded.getBytes(Charsets.UTF_8)), Charsets.UTF_8);

int semiColonPos = basicAuthDecoded.indexOf(':');
if (semiColonPos <= 0) {
throw new UnauthorizedException("Invalid credentials : " + basicAuthDecoded);
}
String login = basicAuthDecoded.substring(0, semiColonPos);
String password = basicAuthDecoded.substring(semiColonPos + 1);
return new String[] {login, password};
}

private UserDto authenticate(String login, String password, HttpServletRequest request) {
if (isEmpty(password)) {
return authenticateFromUserToken(login);
} else {
return credentialsAuthenticator.authenticate(login, password, request);
}
}

private UserDto authenticateFromUserToken(String token) {
Optional<String> authenticatedLogin = userTokenAuthenticator.authenticate(token);
if (!authenticatedLogin.isPresent()) {
throw new UnauthorizedException("Token doesn't exist");
}
DbSession dbSession = dbClient.openSession(false);
try {
UserDto userDto = dbClient.userDao().selectActiveUserByLogin(dbSession, authenticatedLogin.get());
if (userDto == null) {
throw new UnauthorizedException("User doesn't exist");
}
return userDto;
} finally {
dbClient.closeSession(dbSession);
}
}

}
Expand Up @@ -23,7 +23,6 @@
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static org.elasticsearch.common.Strings.isNullOrEmpty; import static org.elasticsearch.common.Strings.isNullOrEmpty;
import static org.sonar.server.authentication.CookieUtils.findCookie; import static org.sonar.server.authentication.CookieUtils.findCookie;
import static org.sonar.server.user.ServerUserSession.createForUser;


import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Claims;
Expand All @@ -41,9 +40,6 @@
import org.sonar.db.DbClient; import org.sonar.db.DbClient;
import org.sonar.db.DbSession; import org.sonar.db.DbSession;
import org.sonar.db.user.UserDto; import org.sonar.db.user.UserDto;
import org.sonar.server.exceptions.UnauthorizedException;
import org.sonar.server.user.ServerUserSession;
import org.sonar.server.user.ThreadLocalUserSession;


@ServerSide @ServerSide
public class JwtHttpHandler { public class JwtHttpHandler {
Expand Down Expand Up @@ -71,20 +67,17 @@ public class JwtHttpHandler {
// This timeout is used to disconnect the user we he has not browse any page for a while // This timeout is used to disconnect the user we he has not browse any page for a while
private final int sessionTimeoutInSeconds; private final int sessionTimeoutInSeconds;
private final JwtCsrfVerifier jwtCsrfVerifier; private final JwtCsrfVerifier jwtCsrfVerifier;
private final ThreadLocalUserSession threadLocalUserSession;


public JwtHttpHandler(System2 system2, DbClient dbClient, Server server, Settings settings, JwtSerializer jwtSerializer, JwtCsrfVerifier jwtCsrfVerifier, public JwtHttpHandler(System2 system2, DbClient dbClient, Server server, Settings settings, JwtSerializer jwtSerializer, JwtCsrfVerifier jwtCsrfVerifier) {
ThreadLocalUserSession threadLocalUserSession) {
this.jwtSerializer = jwtSerializer; this.jwtSerializer = jwtSerializer;
this.server = server; this.server = server;
this.dbClient = dbClient; this.dbClient = dbClient;
this.system2 = system2; this.system2 = system2;
this.sessionTimeoutInSeconds = getSessionTimeoutInSeconds(settings); this.sessionTimeoutInSeconds = getSessionTimeoutInSeconds(settings);
this.jwtCsrfVerifier = jwtCsrfVerifier; this.jwtCsrfVerifier = jwtCsrfVerifier;
this.threadLocalUserSession = threadLocalUserSession;
} }


void generateToken(UserDto user, HttpServletResponse response) { public void generateToken(UserDto user, HttpServletResponse response) {
String csrfState = jwtCsrfVerifier.generateState(response, sessionTimeoutInSeconds); String csrfState = jwtCsrfVerifier.generateState(response, sessionTimeoutInSeconds);


String token = jwtSerializer.encode(new JwtSerializer.JwtSession( String token = jwtSerializer.encode(new JwtSerializer.JwtSession(
Expand All @@ -94,22 +87,23 @@ void generateToken(UserDto user, HttpServletResponse response) {
LAST_REFRESH_TIME_PARAM, system2.now(), LAST_REFRESH_TIME_PARAM, system2.now(),
CSRF_JWT_PARAM, csrfState))); CSRF_JWT_PARAM, csrfState)));
response.addCookie(createCookie(JWT_COOKIE, token, sessionTimeoutInSeconds)); response.addCookie(createCookie(JWT_COOKIE, token, sessionTimeoutInSeconds));
threadLocalUserSession.set(createForUser(dbClient, user));
} }


void validateToken(HttpServletRequest request, HttpServletResponse response) { public Optional<UserDto> validateToken(HttpServletRequest request, HttpServletResponse response) {
validate(request, response); Optional<UserDto> userDto = validate(request, response);
if (!threadLocalUserSession.isLoggedIn()) { if (userDto.isPresent()) {
threadLocalUserSession.set(ServerUserSession.createForAnonymous(dbClient)); return userDto;
} }
removeToken(response);
return Optional.empty();
} }


private void validate(HttpServletRequest request, HttpServletResponse response) { private Optional<UserDto> validate(HttpServletRequest request, HttpServletResponse response) {
Optional<String> token = getTokenFromCookie(request); Optional<String> token = getTokenFromCookie(request);
if (!token.isPresent()) { if (!token.isPresent()) {
return; return Optional.empty();
} }
validateToken(token.get(), request, response); return validateToken(token.get(), request, response);
} }


private static Optional<String> getTokenFromCookie(HttpServletRequest request) { private static Optional<String> getTokenFromCookie(HttpServletRequest request) {
Expand All @@ -125,18 +119,16 @@ private static Optional<String> getTokenFromCookie(HttpServletRequest request) {
return Optional.of(token); return Optional.of(token);
} }


private void validateToken(String tokenEncoded, HttpServletRequest request, HttpServletResponse response) { private Optional<UserDto> validateToken(String tokenEncoded, HttpServletRequest request, HttpServletResponse response) {
Optional<Claims> claims = jwtSerializer.decode(tokenEncoded); Optional<Claims> claims = jwtSerializer.decode(tokenEncoded);
if (!claims.isPresent()) { if (!claims.isPresent()) {
removeToken(response); return Optional.empty();
return;
} }


Date now = new Date(system2.now()); Date now = new Date(system2.now());
Claims token = claims.get(); Claims token = claims.get();
if (now.after(DateUtils.addSeconds(token.getIssuedAt(), SESSION_DISCONNECT_IN_SECONDS))) { if (now.after(DateUtils.addSeconds(token.getIssuedAt(), SESSION_DISCONNECT_IN_SECONDS))) {
removeToken(response); return Optional.empty();
return;
} }
jwtCsrfVerifier.verifyState(request, (String) token.get(CSRF_JWT_PARAM)); jwtCsrfVerifier.verifyState(request, (String) token.get(CSRF_JWT_PARAM));


Expand All @@ -146,10 +138,9 @@ private void validateToken(String tokenEncoded, HttpServletRequest request, Http


Optional<UserDto> user = selectUserFromDb(token.getSubject()); Optional<UserDto> user = selectUserFromDb(token.getSubject());
if (!user.isPresent()) { if (!user.isPresent()) {
removeToken(response); return Optional.empty();
throw new UnauthorizedException("User does not exist");
} }
threadLocalUserSession.set(createForUser(dbClient, user.get())); return Optional.of(user.get());
} }


private static Date getLastRefreshDate(Claims token) { private static Date getLastRefreshDate(Claims token) {
Expand All @@ -167,7 +158,6 @@ private void refreshToken(Claims token, HttpServletResponse response) {
void removeToken(HttpServletResponse response) { void removeToken(HttpServletResponse response) {
response.addCookie(createCookie(JWT_COOKIE, null, 0)); response.addCookie(createCookie(JWT_COOKIE, null, 0));
jwtCsrfVerifier.removeState(response); jwtCsrfVerifier.removeState(response);
threadLocalUserSession.remove();
} }


private Cookie createCookie(String name, @Nullable String value, int expirationInSeconds) { private Cookie createCookie(String name, @Nullable String value, int expirationInSeconds) {
Expand Down
Expand Up @@ -30,16 +30,24 @@
import org.sonar.api.server.authentication.OAuth2IdentityProvider; import org.sonar.api.server.authentication.OAuth2IdentityProvider;
import org.sonar.api.server.authentication.UserIdentity; import org.sonar.api.server.authentication.UserIdentity;
import org.sonar.api.utils.MessageException; import org.sonar.api.utils.MessageException;
import org.sonar.db.DbClient;
import org.sonar.db.user.UserDto; import org.sonar.db.user.UserDto;
import org.sonar.server.user.ServerUserSession;
import org.sonar.server.user.ThreadLocalUserSession;


public class OAuth2ContextFactory { public class OAuth2ContextFactory {


private final DbClient dbClient;
private final ThreadLocalUserSession threadLocalUserSession;
private final UserIdentityAuthenticator userIdentityAuthenticator; private final UserIdentityAuthenticator userIdentityAuthenticator;
private final Server server; private final Server server;
private final OAuthCsrfVerifier csrfVerifier; private final OAuthCsrfVerifier csrfVerifier;
private final JwtHttpHandler jwtHttpHandler; private final JwtHttpHandler jwtHttpHandler;


public OAuth2ContextFactory(UserIdentityAuthenticator userIdentityAuthenticator, Server server, OAuthCsrfVerifier csrfVerifier, JwtHttpHandler jwtHttpHandler) { public OAuth2ContextFactory(DbClient dbClient, ThreadLocalUserSession threadLocalUserSession, UserIdentityAuthenticator userIdentityAuthenticator, Server server,
OAuthCsrfVerifier csrfVerifier, JwtHttpHandler jwtHttpHandler) {
this.dbClient = dbClient;
this.threadLocalUserSession = threadLocalUserSession;
this.userIdentityAuthenticator = userIdentityAuthenticator; this.userIdentityAuthenticator = userIdentityAuthenticator;
this.server = server; this.server = server;
this.csrfVerifier = csrfVerifier; this.csrfVerifier = csrfVerifier;
Expand Down Expand Up @@ -117,6 +125,7 @@ public void redirectToRequestedPage() {
public void authenticate(UserIdentity userIdentity) { public void authenticate(UserIdentity userIdentity) {
UserDto userDto = userIdentityAuthenticator.authenticate(userIdentity, identityProvider); UserDto userDto = userIdentityAuthenticator.authenticate(userIdentity, identityProvider);
jwtHttpHandler.generateToken(userDto, response); jwtHttpHandler.generateToken(userDto, response);
threadLocalUserSession.set(ServerUserSession.createForUser(dbClient, userDto));
} }
} }
} }

0 comments on commit d82358c

Please sign in to comment.