/******************************************************************************* * Copyright (c) 2017 Synopsys, Inc * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Synopsys, Inc - initial implementation and documentation *******************************************************************************/ package jenkins.plugins.coverity; import java.io.FileNotFoundException; import java.io.IOException; import java.net.ConnectException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.SocketException; import java.net.URL; import java.net.UnknownHostException; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Logger; import javax.xml.ws.soap.SOAPFaultException; import org.apache.commons.lang.StringUtils; import org.kohsuke.stapler.DataBoundConstructor; import com.coverity.ws.v9.ConfigurationService; import com.coverity.ws.v9.ConfigurationServiceService; import com.coverity.ws.v9.CovRemoteServiceException_Exception; import com.coverity.ws.v9.DefectService; import com.coverity.ws.v9.GroupIdDataObj; import com.coverity.ws.v9.GroupDataObj; import com.coverity.ws.v9.MergedDefectDataObj; import com.coverity.ws.v9.MergedDefectFilterSpecDataObj; import com.coverity.ws.v9.MergedDefectsPageDataObj; import com.coverity.ws.v9.PageSpecDataObj; import com.coverity.ws.v9.PermissionDataObj; import com.coverity.ws.v9.ProjectDataObj; import com.coverity.ws.v9.ProjectFilterSpecDataObj; import com.coverity.ws.v9.RoleAssignmentDataObj; import com.coverity.ws.v9.RoleDataObj; import com.coverity.ws.v9.SnapshotScopeSpecDataObj; import com.coverity.ws.v9.StreamDataObj; import com.coverity.ws.v9.StreamFilterSpecDataObj; import com.coverity.ws.v9.StreamIdDataObj; import com.coverity.ws.v9.UserDataObj; import com.google.common.collect.ImmutableList; import hudson.util.FormValidation; import jenkins.plugins.coverity.ws.WebServiceFactory; /** * Represents one Coverity Integrity Manager server. Abstracts functions like getting streams and defects. */ public class CIMInstance { private static final Logger logger = Logger.getLogger(CIMStream.class.getName()); /** * Pattern to ignore streams - this is used to filter out internal DA streams, which are irrelevant to this plugin */ public static final String STREAM_NAME_IGNORE_PATTERN = "__internal_.*"; /** * The id for this instance, used as a key in CoverityPublisher */ private final String name; /** * The host name for the CIM server */ private final String host; /** * The port for the CIM server (this is the HTTP port and not the data port) */ private final int port; /** * The commit port for the CIM server. This is only for cov-commit-defects. */ private final int dataPort; /** * Username for connecting to the CIM server */ private final String user; /** * Password for connecting to the CIM server */ private final String password; /** * Use SSL */ private final boolean useSSL; /** * cached webservice port for Configuration service */ private transient ConfigurationServiceService configurationServiceService; @DataBoundConstructor public CIMInstance(String name, String host, int port, String user, String password, boolean useSSL, int dataPort) { this.name = name; this.host = host; this.port = port; this.user = user; this.password = password; this.useSSL = useSSL; this.dataPort = dataPort; } public String getHost() { return host; } public int getPort() { return port; } public String getName() { return name; } public String getUser() { return user; } public String getPassword() { return password; } public boolean isUseSSL() { return useSSL; } public int getDataPort() { return dataPort; } /** * Returns a Defect service client using v9 web services. */ public DefectService getDefectService() throws IOException { return WebServiceFactory.getInstance().getDefectService(this); } /** * Returns a Configuration service client using v9 web services. */ public ConfigurationService getConfigurationService() throws IOException { return WebServiceFactory.getInstance().getConfigurationService(this); } public ProjectDataObj getProject(String projectId) throws IOException, CovRemoteServiceException_Exception { List<ProjectDataObj> projects = new ArrayList<>(); try { ProjectFilterSpecDataObj filterSpec = new ProjectFilterSpecDataObj(); filterSpec.setNamePattern(projectId); projects = getConfigurationService().getProjects(filterSpec); } catch (Exception e) { logger.warning("Error getting project " + projectId + " from instance " + name + " (" + host + ":" + port + ")"); } if(projects.size() == 0) { return null; } else { return projects.get(0); } } private transient Map<String, Long> projectKeys; public Long getProjectKey(String projectId) throws IOException, CovRemoteServiceException_Exception { if(projectKeys == null) { projectKeys = new ConcurrentHashMap<String, Long>(); } Long result = projectKeys.get(projectId); if(result == null) { ProjectDataObj project = getProject(projectId); if (project != null) { result = project.getProjectKey(); projectKeys.put(projectId, result); } } return result; } public List<ProjectDataObj> getProjects() throws IOException, CovRemoteServiceException_Exception { try { return getConfigurationService().getProjects(new ProjectFilterSpecDataObj()); } catch (Exception e) { logger.warning("Error getting projects from instance " + name + " (" + host + ":" + port + ")"); } return new ArrayList<>(); } /** * Returns a StreamDataObj for a given streamId. This object must be not null in order to avoid null pointer exceptions. * If the stream is not found an exception explaining the issue is raised. */ public StreamDataObj getStream(String streamId) throws IOException, CovRemoteServiceException_Exception { StreamFilterSpecDataObj filter = new StreamFilterSpecDataObj(); filter.setNamePattern(streamId); List<StreamDataObj> streams = getConfigurationService().getStreams(filter); if(streams == null || streams.isEmpty()) { throw new IOException("An error occurred while retrieving streams for the given project. Could not find stream: " + streamId); } else { StreamDataObj streamDataObj = streams.get(0); if(streamDataObj == null){ throw new IOException("An error occurred while retrieving streams for the given project. Could not find stream: " + streamId); } else { return streams.get(0); } } } public FormValidation doCheck() throws IOException { StringBuilder errorMessage = new StringBuilder(); errorMessage.append("\"" + user + "\" does not have following permission(s): "); try { URL url = WebServiceFactory.getInstance().getURL(this); int responseCode = getURLResponseCode(new URL(url, WebServiceFactory.CONFIGURATION_SERVICE_V9_WSDL)); if(responseCode != 200) { return FormValidation.error("Coverity web services were not detected. Connection attempt responded with " + responseCode + ", check Coverity Connect version (minimum supported version is " + CoverityVersion.MINIMUM_SUPPORTED_VERSION.getEffectiveVersion().getEffectiveVersion() + ")."); } List<String> missingPermission = new ArrayList<String>(); if (!checkUserPermission(missingPermission, false) && !missingPermission.isEmpty()){ for (String permission : missingPermission){ errorMessage.append("\"" + permission + "\" "); } return FormValidation.error(errorMessage.toString()); } // check for missing global permissions to warn users // in some cases users could have group permissions but only a specific project, stream, etc. List<String> missingGlobalPermission = new ArrayList<String>(); if (!checkUserPermission(missingGlobalPermission, true) && !missingGlobalPermission.isEmpty()) { StringBuilder warningMessage = new StringBuilder(); warningMessage.append("\"" + user + "\" does not have following global permission(s): "); for (String permission : missingGlobalPermission){ warningMessage.append("\"" + permission + "\" "); } return FormValidation.warning(warningMessage.toString()); } return FormValidation.ok("Successfully connected to the instance."); } catch(UnknownHostException e) { return FormValidation.error("Host name unknown"); } catch(ConnectException e) { return FormValidation.error("Connection refused"); } catch(SocketException e) { return FormValidation.error("Error connecting to CIM. Please check your connection settings."); } catch(SOAPFaultException e){ if (StringUtils.isNotEmpty(e.getMessage())){ if (e.getMessage().contains("User " + user + " Doesn't have permissions to perform {invokeWS}")) { return FormValidation.error(errorMessage.append("\"Access web services\"").toString()); } return FormValidation.error(e.getMessage()); } return FormValidation.error(e, "An unexpected error occurred."); } catch (Throwable e) { String javaVersion = System.getProperty("java.version"); if(javaVersion.startsWith("1.6.0_")) { int patch = Integer.parseInt(javaVersion.substring(javaVersion.indexOf('_') + 1)); if(patch < 26) { return FormValidation.error(e, "Please use Java 1.6.0_26 or later to run Jenkins."); } } return FormValidation.error(e, "An unexpected error occurred."); } } private int getURLResponseCode(URL url) throws IOException { try { HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.connect(); conn.getInputStream(); return conn.getResponseCode(); } catch(FileNotFoundException e) { return 404; } } public ImmutableList<String> getCimInstanceCheckers() throws IOException, CovRemoteServiceException_Exception { final List<String> checkerNames = this.getConfigurationService().getCheckerNames(); Collections.sort(checkerNames); return ImmutableList.copyOf(checkerNames); } /** * A user requires 3 sets of permissions in order to use Coverity plugin. * The required permissions are "WebService Access", "Commit To a Stream", and "View Issues". * This method check whether the configured user have all the required permissions. * Returns true if the user have all the permissions, otherwise, return false with * a list of missing permissions. */ private boolean checkUserPermission(List<String> missingPermissions, boolean onlyCheckGlobal) throws IOException, com.coverity.ws.v9.CovRemoteServiceException_Exception{ boolean canCommit = false; boolean canViewIssues = false; UserDataObj userData = getConfigurationService().getUser(user); if (userData != null){ if (userData.isSuperUser()){ return true; } // Start the queue with direct role assignments to the user final LinkedList<RoleAssignmentDataObj> roleAssignments = new LinkedList<>(userData.getRoleAssignments()); final Iterator<String> groupNameIterator = userData.getGroups().iterator(); while (!(canCommit && canViewIssues) && (!roleAssignments.isEmpty() || groupNameIterator.hasNext())) { // No more role assignments, add more from the next available group if (roleAssignments.isEmpty()) { final GroupIdDataObj groupId = new GroupIdDataObj(); groupId.setName(groupNameIterator.next()); final GroupDataObj group = getConfigurationService().getGroup(groupId); if (group != null) { roleAssignments.addAll(group.getRoleAssignments()); } } // The user still has direct role assignments or a few more were added from a group if (!roleAssignments.isEmpty()) { final RoleAssignmentDataObj roleAssignment = roleAssignments.removeFirst(); final RoleDataObj roleData = getConfigurationService().getRole(roleAssignment.getRoleId()); if (onlyCheckGlobal && !roleAssignment.getType().equals("global")) continue; if (roleData != null) { for (PermissionDataObj permission : roleData.getPermissionDataObjs()) { if (permission.getPermissionValue().equalsIgnoreCase("commitToStream")) { canCommit = true; } else if (permission.getPermissionValue().equalsIgnoreCase("viewDefects")) { canViewIssues = true; } } } } } } if (!canCommit){ missingPermissions.add("Commit to a stream"); } if (!canViewIssues){ missingPermissions.add("View issues"); } return canCommit && canViewIssues; } @Override public int hashCode() { int result = name != null ? name.hashCode() : 0; result = 31 * result + (host != null ? host.hashCode() : 0); result = 31 * result + port; result = 31 * result + dataPort; result = 31 * result + (user != null ? user.hashCode() : 0); result = 31 * result + (password != null ? password.hashCode() : 0); return result; } }