/*
* Copyright (C) 2010 JFrog Ltd.
*
* 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.jfrog.bamboo.admin;
import com.atlassian.bamboo.bandana.*;
import com.atlassian.bamboo.variable.CustomVariableContext;
import com.atlassian.bandana.BandanaManager;
import com.atlassian.spring.container.ContainerManager;
import com.google.common.collect.Lists;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.ObjectWriter;
import org.jetbrains.annotations.Nullable;
import org.jfrog.bamboo.security.EncryptionHelper;
import org.jfrog.bamboo.util.BuildInfoLog;
import org.jfrog.bamboo.util.TaskUtils;
import org.jfrog.build.extractor.clientConfiguration.client.ArtifactoryBuildInfoClient;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Global Artifactory server configuration manager
*
* @author Noam Y. Tenne
*/
public class ServerConfigManager implements Serializable {
private transient Logger log = Logger.getLogger(ServerConfigManager.class);
private static final String ARTIFACTORY_CONFIG_KEY = "org.jfrog.bamboo.server.configurations.v2";
private static final String BINTRAY_CONFIG_KEY = "org.jfrog.bamboo.bintray.configurations.v2";
private final List<ServerConfig> configuredServers = new CopyOnWriteArrayList<>();
private BintrayConfig bintrayConfig;
private BandanaManager bandanaManager = null;
private AtomicLong nextAvailableId = new AtomicLong(0);
private CustomVariableContext customVariableContext;
public List<ServerConfig> getAllServerConfigs() {
return Lists.newArrayList(configuredServers);
}
public ServerConfig getServerConfigById(long id) {
for (ServerConfig configuredServer : configuredServers) {
if (configuredServer.getId() == id) {
return configuredServer;
}
}
return null;
}
public static ServerConfigManager getInstance() {
ServerConfigManager serverConfigManager = new ServerConfigManager();
ContainerManager.autowireComponent(serverConfigManager);
return serverConfigManager;
}
public void addServerConfiguration(ServerConfig serverConfig) {
serverConfig.setId(nextAvailableId.getAndIncrement());
configuredServers.add(serverConfig);
try {
persist();
} catch (IllegalAccessException | UnsupportedEncodingException e) {
log.error("Could not add Artifactory configuration.", e);
}
}
public void deleteServerConfiguration(final long id) {
for (ServerConfig configuredServer : configuredServers) {
if (configuredServer.getId() == id) {
configuredServers.remove(configuredServer);
try {
persist();
} catch (IllegalAccessException | UnsupportedEncodingException e) {
log.error("Could not delete Artifactory configuration.", e);
}
break;
}
}
}
public void updateServerConfiguration(ServerConfig updated) {
for (ServerConfig configuredServer : configuredServers) {
if (configuredServer.getId() == updated.getId()) {
configuredServer.setUrl(updated.getUrl());
configuredServer.setUsername(updated.getUsername());
configuredServer.setPassword(updated.getPassword());
configuredServer.setTimeout(updated.getTimeout());
try {
persist();
} catch (IllegalAccessException | UnsupportedEncodingException e) {
log.error("Could not update Artifactory configuration.", e);
}
break;
}
}
}
public void updateBintrayConfiguration(BintrayConfig bintrayConfig) {
this.bintrayConfig = bintrayConfig;
try {
persistBintray();
} catch (IllegalAccessException | UnsupportedEncodingException e) {
log.error("Could not update Bintray configuration.", e);
}
}
public void setBandanaManager(BandanaManager bandanaManager) {
this.bandanaManager = bandanaManager;
try {
setArtifactoryServers(bandanaManager);
} catch (InstantiationException | IllegalAccessException | IOException e) {
log.error("Could not load Artifactory configuration.", e);
}
try {
setBintrayConfigurations(bandanaManager);
} catch (InstantiationException | IllegalAccessException | IOException e) {
log.error("Could not load Bintray configuration.", e);
}
}
public boolean isMissedMigration() {
Iterator keysIterator = bandanaManager.getKeys(PlanAwareBandanaContext.GLOBAL_CONTEXT).iterator();
boolean isMissedMigration = false;
while (keysIterator.hasNext()) {
String key = (String)keysIterator.next();
// If the new key exists no migration needed.
if (key.equals(ARTIFACTORY_CONFIG_KEY) || key.equals(BINTRAY_CONFIG_KEY)) {
return false;
}
// isMissedMigration will be true only if already found a key from the old plugin
if (!isMissedMigration) {
isMissedMigration = key.contains("org.jfrog.bamboo");
}
}
return isMissedMigration;
}
private void setBintrayConfigurations(BandanaManager bandanaManager)
throws IOException, InstantiationException, IllegalAccessException {
String existingBintrayConfig = (String) bandanaManager.getValue(PlanAwareBandanaContext.GLOBAL_CONTEXT, BINTRAY_CONFIG_KEY);
if (existingBintrayConfig != null && !"".equals(existingBintrayConfig)) {
BintrayConfig tempBintrayConfig = getObjectFromStringXml(existingBintrayConfig, BintrayConfig.class);
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
String json = ow.writeValueAsString(tempBintrayConfig);
tempBintrayConfig = new ObjectMapper().readValue(json, BintrayConfig.class);
bintrayConfig = decryptExistingBintrayConfig(tempBintrayConfig);
}
}
private void setArtifactoryServers(BandanaManager bandanaManager)
throws IOException, InstantiationException, IllegalAccessException {
String existingArtifactoryConfig = (String) bandanaManager.getValue(PlanAwareBandanaContext.GLOBAL_CONTEXT, ARTIFACTORY_CONFIG_KEY);
if (StringUtils.isNotBlank(existingArtifactoryConfig)) {
List<ServerConfig> serverConfigList = getServersFromXml(existingArtifactoryConfig);
for (Object serverConfig : serverConfigList) {
// Because of some class loader issues we had to get a workaround,
// we serialize and deserialize the serverConfig object.
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
String json = ow.writeValueAsString(serverConfig);
ServerConfig tempServerConfig = new ObjectMapper().readValue(json, ServerConfig.class);
if (nextAvailableId.get() <= tempServerConfig.getId()) {
nextAvailableId.set(tempServerConfig.getId() + 1);
}
configuredServers.add(new ServerConfig(tempServerConfig.getId(), tempServerConfig.getUrl(), tempServerConfig.getUsername(),
EncryptionHelper.decrypt(tempServerConfig.getPassword()), tempServerConfig.getTimeout()));
}
}
}
private BintrayConfig decryptExistingBintrayConfig(BintrayConfig bintrayConfig) {
String bintrayApi = bintrayConfig.getBintrayApiKey();
String sonatypeOssPassword = bintrayConfig.getSonatypeOssPassword();
bintrayApi = TaskUtils.decryptIfNeeded(bintrayApi);
sonatypeOssPassword = TaskUtils.decryptIfNeeded(sonatypeOssPassword);
return new BintrayConfig(bintrayConfig.getBintrayUsername(), bintrayApi,
bintrayConfig.getSonatypeOssUsername(), sonatypeOssPassword);
}
public void setBintrayConfig(BintrayConfig bintrayConfig) {
this.bintrayConfig = bintrayConfig;
}
public BintrayConfig getBintrayConfig() {
return bintrayConfig;
}
public List<String> getDeployableRepos(long serverId) {
return getDeployableRepos(serverId, null, null);
}
public List<String> getDeployableRepos(long serverId, HttpServletRequest req, HttpServletResponse resp) {
ServerConfig serverConfig = getServerConfigById(serverId);
if (serverConfig == null) {
log.error("Error while retrieving target repository list: Could not find Artifactory server " +
"configuration by the ID " + serverId);
return Lists.newArrayList();
}
ArtifactoryBuildInfoClient client;
String serverUrl = substituteVariables(serverConfig.getUrl());
String username = null;
String password = null;
if (req != null) {
username = req.getParameter("user");
password = req.getParameter("password");
}
if (StringUtils.isNotBlank(username) && StringUtils.isNotBlank(password)) {
password = TaskUtils.decryptIfNeeded(password);
} else {
username = serverConfig.getUsername();
password = serverConfig.getPassword();
}
username = substituteVariables(username);
password = substituteVariables(password);
if (StringUtils.isBlank(username)) {
client = new ArtifactoryBuildInfoClient(serverUrl, new BuildInfoLog(log));
} else {
client = new ArtifactoryBuildInfoClient(serverUrl, username, password,
new BuildInfoLog(log));
}
client.setConnectionTimeout(serverConfig.getTimeout());
try {
return client.getLocalRepositoriesKeys();
} catch (IOException ioe) {
log.error("Error while retrieving target repository list from: " + serverUrl, ioe);
try {
if (resp != null && ioe.getMessage().contains("401"))
resp.sendError(HttpServletResponse.SC_UNAUTHORIZED);
if (resp != null && ioe.getMessage().contains("404"))
resp.sendError(HttpServletResponse.SC_NOT_FOUND);
} catch (IOException e) {
log.error("Error while sending error to response", e);
}
return Lists.newArrayList();
}
}
/**
* Substitute (replace) Bamboo variable names with their defined values
*/
public String substituteVariables(String s) {
return s != null ? customVariableContext.substituteString(s) : null;
}
public List<String> getResolvingRepos(long serverId, HttpServletRequest req, HttpServletResponse resp) {
ServerConfig serverConfig = getServerConfigById(serverId);
if (serverConfig == null) {
log.error("Error while retrieving resolving repository list: Could not find Artifactory server " +
"configuration by the ID " + serverId);
return Lists.newArrayList();
}
ArtifactoryBuildInfoClient client;
String serverUrl = substituteVariables(serverConfig.getUrl());
String username;
String password;
if (StringUtils.isNotBlank(req.getParameter("user")) && StringUtils.isNotBlank(req.getParameter("password"))) {
username = substituteVariables(req.getParameter("user"));
password = substituteVariables(TaskUtils.decryptIfNeeded(req.getParameter("password")));
} else {
username = substituteVariables(serverConfig.getUsername());
password = substituteVariables(serverConfig.getPassword());
}
if (StringUtils.isBlank(username)) {
client = new ArtifactoryBuildInfoClient(serverUrl, new BuildInfoLog(log));
} else {
client = new ArtifactoryBuildInfoClient(serverUrl, username, password,
new BuildInfoLog(log));
}
client.setConnectionTimeout(serverConfig.getTimeout());
try {
return client.getVirtualRepositoryKeys();
} catch (IOException ioe) {
log.error("Error while retrieving resolving repository list from: " + serverUrl, ioe);
try {
if (ioe.getMessage().contains("401"))
resp.sendError(HttpServletResponse.SC_UNAUTHORIZED);
if (ioe.getMessage().contains("404"))
resp.sendError(HttpServletResponse.SC_NOT_FOUND);
} catch (IOException e) {
log.error("Error while sending error to response", e);
}
return Lists.newArrayList();
}
}
private synchronized void persist() throws IllegalAccessException, UnsupportedEncodingException {
List<ServerConfig> serverConfigs = Lists.newArrayList();
for (ServerConfig serverConfig : configuredServers) {
serverConfigs.add(new ServerConfig(serverConfig.getId(), serverConfig.getUrl(), serverConfig.getUsername(),
EncryptionHelper.encrypt(serverConfig.getPassword()), serverConfig.getTimeout()));
}
String serverConfigsString = toXMLString(serverConfigs);
bandanaManager.setValue(PlanAwareBandanaContext.GLOBAL_CONTEXT, ARTIFACTORY_CONFIG_KEY, serverConfigsString);
}
private synchronized void persistBintray() throws IllegalAccessException, UnsupportedEncodingException {
if (bintrayConfig != null) {
String stringbintrayConfig = toXMLString(createEncryptedBintrayConfig(bintrayConfig));
bandanaManager.setValue(PlanAwareBandanaContext.GLOBAL_CONTEXT, BINTRAY_CONFIG_KEY, stringbintrayConfig);
}
}
private BintrayConfig createEncryptedBintrayConfig(BintrayConfig bintrayConfig) {
BintrayConfig encConfig = new BintrayConfig();
String apiKey = EncryptionHelper.encrypt(bintrayConfig.getBintrayApiKey());
String sonatypeOssPassword = bintrayConfig.getSonatypeOssPassword();
encConfig.setBintrayApiKey(apiKey);
encConfig.setSonatypeOssPassword(EncryptionHelper.encrypt(sonatypeOssPassword));
encConfig.setBintrayUsername(bintrayConfig.getBintrayUsername());
encConfig.setSonatypeOssUsername(bintrayConfig.getSonatypeOssUsername());
return encConfig;
}
public void setCustomVariableContext(CustomVariableContext customVariableContext) {
this.customVariableContext = customVariableContext;
}
public class BandanaContext extends PlanAwareBandanaContext{
public BandanaContext(@Nullable BambooBandanaContext parentContext, long planId, @Nullable String pluginKey) {
super(parentContext, planId, pluginKey);
}
}
private List<ServerConfig> getServersFromXml(String stringXml) throws IllegalAccessException, InstantiationException {
List<ServerConfig> serverConfigs = Lists.newArrayList();
List<String> stringServerConfigs = findAllObjects(ServerConfig.class, stringXml);
for (String stringServerConfig : stringServerConfigs) {
serverConfigs.add(getObjectFromStringXml(stringServerConfig, ServerConfig.class));
}
return serverConfigs;
}
private <T> T getObjectFromStringXml(String stringT, Class<T> tClass) throws IllegalAccessException, InstantiationException {
T object = tClass.newInstance();
boolean accsessable;
String value;
for (Field field : tClass.getDeclaredFields()) {
accsessable = field.isAccessible();
field.setAccessible(true);
value = findFirstObject(field.getName(), stringT, true);
if (field.getType().equals(long.class)) {
field.set(object, Long.parseLong(value));
} else if (field.getType().equals(int.class)) {
field.set(object, Integer.parseInt(value));
} else {
field.set(object, findFirstObject(field.getName(), stringT, true));
}
field.setAccessible(accsessable);
}
return object;
}
private List<String> findAllObjects(Class providedClass, String scannedString){
List<String> foundStrings = Lists.newArrayList();
String foundString = findFirstObject(providedClass.getSimpleName(), scannedString, false);
while (!"".equals(foundString)) {
foundStrings.add(foundString);
scannedString = scannedString.replaceFirst(foundString, "");
foundString = findFirstObject(providedClass.getSimpleName(), scannedString, false);
}
return foundStrings;
}
/**
* Returns the found string or empty string if not found
* @param objectToFind
* @param stringToScan
* @return
*/
private String findFirstObject(String objectToFind, String stringToScan, boolean dataOnly) {
String patternString = String.format("<%s>?(.*?)</%s>", objectToFind, objectToFind);
Pattern pattern = Pattern.compile(patternString);
Matcher matcher = pattern.matcher(stringToScan);
if (matcher.find()) {
if (dataOnly) {
return new String(Base64.getDecoder().decode(matcher.group(1).getBytes()));
}
return matcher.group(0);
}
return "";
}
private String toXMLString(List<ServerConfig> serverConfigs) throws IllegalAccessException, UnsupportedEncodingException {
StringBuilder stringBuilder = new StringBuilder();
openTag(stringBuilder, "List");
for (ServerConfig serverConfig : serverConfigs) {
stringBuilder.append(toXMLString(serverConfig));
}
closeTag(stringBuilder, "List");
return stringBuilder.toString();
}
private String toXMLString(Object object) throws IllegalAccessException, UnsupportedEncodingException {
StringBuilder stringBuilder = new StringBuilder();
openTag(stringBuilder, object.getClass().getSimpleName());
for (Field field : object.getClass().getDeclaredFields()) {
field.setAccessible(true);
String value = field.get(object) == null ? "" : field.get(object).toString();
appendAttribute(stringBuilder, field.getName(), value);
}
closeTag(stringBuilder, object.getClass().getSimpleName());
return stringBuilder.toString();
}
private void appendAttribute(StringBuilder stringBuilder, String field, String value) throws UnsupportedEncodingException {
openTag(stringBuilder, field);
// Encoding the value to Base64 to prevent saving special chars like % to the database
stringBuilder.append(Base64.getEncoder().encodeToString(value.getBytes("utf-8")));
closeTag(stringBuilder, field);
}
private void openTag(StringBuilder stringBuilder, String fieldName) {
stringBuilder.append("<");
stringBuilder.append(fieldName);
stringBuilder.append(">");
}
private void closeTag(StringBuilder stringBuilder, String fieldName) {
stringBuilder.append("</");
stringBuilder.append(fieldName);
stringBuilder.append(">");
}
}