//////////////////////////////////////////////////////////////////////// // // Copyright (c) 2009-2013 Denim Group, Ltd. // // The contents of this file are subject to the Mozilla Public 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.mozilla.org/MPL/ // // Software distributed under the License is distributed on an "AS IS" // basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the // License for the specific language governing rights and limitations // under the License. // // The Original Code is ThreadFix. // // The Initial Developer of the Original Code is Denim Group, Ltd. // Portions created by Denim Group, Ltd. are Copyright (C) // Denim Group, Ltd. All Rights Reserved. // // Contributor(s): Denim Group, Ltd. // //////////////////////////////////////////////////////////////////////// package com.denimgroup.threadfix.service.defects; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import com.denimgroup.threadfix.data.entities.Defect; import com.denimgroup.threadfix.data.entities.Vulnerability; import com.denimgroup.threadfix.service.SanitizedLogger; import com.microsoft.tfs.core.TFSTeamProjectCollection; import com.microsoft.tfs.core.clients.workitem.WorkItem; import com.microsoft.tfs.core.clients.workitem.WorkItemClient; import com.microsoft.tfs.core.clients.workitem.fields.FieldDefinitionCollection; import com.microsoft.tfs.core.clients.workitem.project.Project; import com.microsoft.tfs.core.clients.workitem.project.ProjectCollection; import com.microsoft.tfs.core.clients.workitem.query.WorkItemCollection; import com.microsoft.tfs.core.exceptions.TECoreException; import com.microsoft.tfs.core.exceptions.TFSUnauthorizedException; import com.microsoft.tfs.core.httpclient.Credentials; import com.microsoft.tfs.core.httpclient.UsernamePasswordCredentials; import com.microsoft.tfs.core.ws.runtime.exceptions.UnauthorizedException; public class TFSDefectTracker extends AbstractDefectTracker { protected static final SanitizedLogger staticLog = new SanitizedLogger("TFSDefectTracker"); // We need to load the native libraries and this seems to be the best spot. // The idea is to use the same code for loading all the libraries but use // string values to specify which folder they are in and which names to look up. static { String osName = System.getProperty("os.name"), osArch = System.getProperty("os.arch"); staticLog.info("Attempting to load libraries for " + osName + "."); String folderName = null, prefix = null, suffix = null; String[] names = null; if (osName == null) { staticLog.error("Received null from System.getProperty(\"os.name\"), " + "something is wrong here."); } else if (osName.startsWith("Windows")) { folderName = "/tfs-native/win32/x86"; if (osArch != null && osArch.contains("64")) { folderName += "_64"; } prefix = "native_"; suffix = ".dll"; names = new String[] { "synchronization", "auth", "console", "filesystem", "messagewindow", "misc", "registry" }; } else if (osName.startsWith("Mac OS")) { folderName = "/tfs-native/macosx"; prefix = "libnative_"; suffix = ".jnilib"; names = new String[] { "auth", "console", "filesystem", "keychain", "misc", "synchronization" }; } else if (osName.startsWith("Linux")) { String archExtension = osArch; if (osArch.equals("amd64")) { archExtension = "x86_64"; } else if (osArch.equals("i386")) { archExtension = "x86"; } folderName = "/tfs-native/linux/" + archExtension; prefix = "libnative_"; suffix = ".so"; names = new String[] { "auth", "console", "filesystem", "misc", "synchronization" }; } else if (osName.equals("hpux") || osName.equals("aix") || osName.equals("solaris")) { folderName = "/tfs-native/" + osName + "/"; prefix = "libnative_"; suffix = ".so"; if (osArch != null && osArch.equals("PA_RISC")) { suffix = ".sl"; } else if (osArch != null && osArch.equals("ppc")) { suffix = ".a"; } } else { staticLog.error("OS name not supported by TFS. " + "The TFS integration will fail."); } if (folderName != null && prefix != null && suffix != null && names != null) { try { URI testUri = TFSDefectTracker.class.getClassLoader() .getResource(folderName).toURI(); String base = testUri.getPath() .replaceFirst("file:", ""); try { for (String library : names) { System.load(base + prefix + library + suffix); } staticLog.info("Successfully loaded native libraries for " + osName + "."); } catch (UnsatisfiedLinkError e) { staticLog.error("Unable to locate one of the libraries.", e); } } catch (URISyntaxException e) { staticLog.error("Unable to convert the path String to a URI.", e); } } else { staticLog.error("Attempt to load TFS native libraries failed.."); } } public TFSDefectTracker() { } private WorkItemClient getClient() { Credentials credentials = new UsernamePasswordCredentials( getUsername(), getPassword()); URI uri = null; try { uri = new URI(getUrl()); } catch (URISyntaxException e) { e.printStackTrace(); } TFSTeamProjectCollection projects = new TFSTeamProjectCollection(uri, credentials); try { return projects.getWorkItemClient(); } catch (UnauthorizedException | TFSUnauthorizedException e) { log.warn("TFSUnauthorizedException encountered, unable to connect to TFS. " + "Check credentials and endpoint."); } return null; } @Override public String createDefect(List<Vulnerability> vulnerabilities, DefectMetadata metadata) { WorkItemClient workItemClient = getClient(); if (workItemClient == null) { log.warn("Unable to create defect."); return null; } Project project = null; try { project = workItemClient.getProjects().get(getProjectName()); } catch (UnauthorizedException | TFSUnauthorizedException e) { log.warn("Ran into TFSUnauthorizedException while trying to retrieve products."); workItemClient.close(); return null; } if (project == null) { log.warn("Product was not found. Unable to create defect."); return null; } WorkItem item = workItemClient.newWorkItem(project .getVisibleWorkItemTypes()[0]); if (item == null) { log.warn("Unable to create item in TFS."); return null; } item.setTitle(metadata.getDescription()); item.getFields().getField("Description") .setValue(makeDescription(vulnerabilities, metadata)); item.getFields().getField("Priority").setValue(metadata.getPriority()); item.save(); String itemId = String.valueOf(item.getID()); workItemClient.close(); return itemId; } @Override public String getBugURL(String endpointURL, String bugID) { return null; } @Override public Map<Defect, Boolean> getMultipleDefectStatus(List<Defect> defectList) { Map<Defect, Boolean> returnMap = new HashMap<Defect, Boolean>(); Map<String, String> stringStatusMap = new HashMap<String, String>(); Map<String, Boolean> openStatusMap = new HashMap<String, Boolean>(); WorkItemClient workItemClient = getClient(); if (workItemClient == null) { log.warn("Updating bug status failed."); return null; } StringBuilder builder = new StringBuilder(); for (Defect defect : defectList) { builder.append(defect.getNativeId() + ","); } String ids = builder.substring(0, builder.length() - 1); String wiqlQuery = "Select ID, State from WorkItems where (id in (" + ids + "))"; // Run the query and get the results. WorkItemCollection workItems = workItemClient.query(wiqlQuery); for (int i = 0; i < workItems.size(); i++) { WorkItem workItem = workItems.getWorkItem(i); stringStatusMap.put(String.valueOf(workItem.getID()), (String) workItem.getFields().getField("State") .getOriginalValue()); openStatusMap.put(String.valueOf(workItem.getID()), workItem.isOpen()); } log.info("Updating bug statuses."); // Find the open or closed status for each defect. for (Defect defect : defectList) { if (defect != null) { returnMap.put(defect, openStatusMap.get(defect.getNativeId())); defect.setStatus(stringStatusMap.get(defect.getNativeId())); } } workItemClient.close(); return returnMap; } @Override public String getProductNames() { log.info("Getting list of product names."); WorkItemClient workItemClient = getClient(); if (workItemClient == null) { log.warn("Unable to retrieve WorkItemClient, returning an unauthorized message."); setLastError("Invalid username / password combination"); return null; } ProjectCollection collection = null; if (workItemClient != null) { try { collection = workItemClient.getProjects(); } catch (UnauthorizedException | TFSUnauthorizedException e) { log.warn("Ran into TFSUnauthorizedException while trying to retrieve products."); setLastError("Invalid username / password combination"); return null; } finally { workItemClient.close(); } } if (collection == null || collection.size() == 0) { log.warn("Collection of projects was null or empty."); return null; } StringBuilder builder = new StringBuilder(); for (Project project : collection) { builder.append(project.getName() + ","); } return builder.subSequence(0, builder.length() - 2).toString(); } @Override public String getProjectIdByName() { WorkItemClient workItemClient = getClient(); if (workItemClient == null) { log.warn("Unable to connect to TFS to retrieve project name."); return null; } Project project = null; try { project = workItemClient.getProjects().get(getProjectName()); } catch (UnauthorizedException | TFSUnauthorizedException e) { log.warn("Ran into TFSUnauthorizedException while trying to retrieve products."); return null; } finally { workItemClient.close(); } if (project == null) { return null; } else { return String.valueOf(project.getID()); } } @Override public ProjectMetadata getProjectMetadata() { log.info("Collecting project metadata"); List<String> statuses = new ArrayList<String>(); List<String> priorities = new ArrayList<String>(); List<String> emptyList = new ArrayList<String>(); emptyList.add("-"); statuses.add("New"); WorkItemClient workItemClient = getClient(); if (workItemClient == null) { log.warn("Unable to connect to TFS, no project metadata could be collected."); return null; } FieldDefinitionCollection collection = workItemClient .getFieldDefinitions(); Collections.addAll(priorities, collection.get("Priority") .getAllowedValues().getValues()); workItemClient.close(); return new ProjectMetadata(emptyList, emptyList, emptyList, statuses, priorities); } @Override public String getTrackerError() { log.info("Returning the error from the tracker."); return "The tracker failed to export a defect."; } @Override public boolean hasValidCredentials() { WorkItemClient workItemClient = getClient(); if (workItemClient == null) { return false; } try { workItemClient.getProjects(); return true; } catch (UnauthorizedException e) { return false; } finally { if (workItemClient != null) { workItemClient.close(); } } } @Override public boolean hasValidProjectName() { if (getProjectName() == null) { return false; } WorkItemClient workItemClient = getClient(); if (workItemClient == null) { log.warn("Unable to connect to TFS, unable to determine whether the project name was valid."); return false; } Project project = null; try { project = workItemClient.getProjects().get(getProjectName()); } catch (UnauthorizedException | TFSUnauthorizedException e) { log.warn("Ran into TFSUnauthorizedException while trying to retrieve products."); return false; } finally { workItemClient.close(); } log.info("Checking Project Name."); return project != null; } @Override public boolean hasValidUrl() { Credentials credentials = new UsernamePasswordCredentials("", ""); URI uri = null; try { uri = new URI(getUrl()); } catch (URISyntaxException e) { log.warn("Invalid syntax for the URL.",e); return false; } TFSTeamProjectCollection projects = new TFSTeamProjectCollection(uri, credentials); try { projects.getWorkItemClient().getProjects(); log.info("No UnauthorizedException was thrown when attempting to connect with blank credentials."); return true; } catch (UnauthorizedException | TFSUnauthorizedException e) { log.info("Got an UnauthorizedException, which means that the TFS url was good."); return true; } catch (TECoreException e) { if (e.getMessage().contains("unable to find valid certification path to requested target")) { setLastError(AbstractDefectTracker.INVALID_CERTIFICATE); log.warn("An invalid or self-signed certificate was found."); } return false; } } }