package com.thundermoose.plugins; import com.atlassian.bitbucket.auth.HttpAuthenticationContext; import com.atlassian.bitbucket.auth.HttpAuthenticationHandler; import com.atlassian.bitbucket.i18n.I18nKey; import com.atlassian.bitbucket.i18n.I18nService; import com.atlassian.bitbucket.user.ApplicationUser; import com.atlassian.bitbucket.user.UserService; import com.thundermoose.plugins.admin.AdminConfig; import com.thundermoose.plugins.admin.AdminConfigDao; import com.thundermoose.plugins.paths.PathMatcher; import com.thundermoose.plugins.user.UserConfig; import com.thundermoose.plugins.user.UserConfigDao; import com.thundermoose.plugins.utils.Encrypter; import com.thundermoose.plugins.utils.EncryptionException; import com.thundermoose.plugins.utils.Utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nullable; import javax.servlet.http.HttpServletRequest; import java.time.Instant; import java.time.LocalDateTime; import java.util.Base64; import java.util.Objects; import java.util.TimeZone; import static org.apache.commons.lang3.StringUtils.isNotEmpty; public class TokenAuthenticationHandler implements HttpAuthenticationHandler { private static final Logger log = LoggerFactory.getLogger(TokenAuthenticationHandler.class); public static final String USER_HEADER = "X-Auth-User"; public static final String TOKEN_HEADER = "X-Auth-Token"; private final UserService userService; private final I18nService i18nService; private final Utils utils; private final UserConfigDao userDao; private final AdminConfigDao adminDao; public TokenAuthenticationHandler(UserService userService, I18nService i18nService, Utils utils, UserConfigDao userDao, AdminConfigDao adminDao) { this.userService = userService; this.i18nService = i18nService; this.utils = utils; this.userDao = userDao; this.adminDao = adminDao; } @Override public void validateAuthentication(HttpAuthenticationContext httpAuthenticationContext) { } @Nullable @Override public ApplicationUser authenticate(HttpAuthenticationContext ctx) { HttpServletRequest request = ctx.getRequest(); String username = request.getHeader(USER_HEADER); String token = request.getHeader(TOKEN_HEADER); String path = request.getRequestURI().replaceFirst(request.getContextPath(), ""); if(isNotEmpty(username) && isNotEmpty(token) && path.startsWith("/rest/")) { if(isTokenValid(path, username, token)) { return userService.getUserByName(username); } } return null; } boolean isTokenValid(String path, String username, String token) { try { AdminConfig config = adminDao.getAdminConfig(); if(!config.getEnabled()) { return false; } Encrypter encrypter = new Encrypter(Base64.getDecoder().decode(config.getKey())); String unencrypted = encrypter.decrypt(token); String[] split = unencrypted.split(":"); if(split.length != 4) { //not a valid token return false; } Integer ttl = adminDao.getAdminConfig().getTtl(); LocalDateTime expiry = LocalDateTime.ofInstant(Instant.ofEpochMilli(Long.parseLong(split[1])), TimeZone.getDefault().toZoneId()).plusDays(ttl); if(Objects.equals(split[0], username) && (ttl <= 0 || LocalDateTime.now().isBefore(expiry))) { //token is valid, see if the path is allowed token access by admin return new PathMatcher( config.getAdminPaths(), config.getProjectPaths(), config.getRepoPaths(), config.getSSHPaths() ).pathAllowed(path) && Objects.equals(userDao.getUserConfig(username).getToken(), token); } else if(Objects.equals(split[0], username) && LocalDateTime.now().isAfter(expiry)) { //token is expired, generate a new one userDao.setUserConfig(username, new UserConfig(encrypter.encrypt(utils.generateTokenForUser(username, config.getTtl())))); } } catch(EncryptionException e) { log.debug("Could not decrypt provided token", e); throw new TokenAuthenticationException(i18nService.getKeyedText(new I18nKey("auth.exception.message"))); } return false; } }