/** * Copyright (c) 2009--2015 Red Hat, Inc. * * This software is licensed to you under the GNU General Public License, * version 2 (GPLv2). There is NO WARRANTY for this software, express or * implied, including the implied warranties of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 * along with this software; if not, see * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. * * Red Hat trademarks are not licensed under GPLv2. No permission is * granted to use or replicate Red Hat trademarks that are incorporated * in this software or its documentation. */ /* * Copyright (c) 2010 SUSE LLC */ package com.redhat.rhn.frontend.xmlrpc.errata; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import com.redhat.rhn.FaultException; import com.redhat.rhn.common.db.datasource.DataResult; import com.redhat.rhn.common.hibernate.HibernateFactory; import com.redhat.rhn.common.hibernate.LookupException; import com.redhat.rhn.common.localization.LocalizationService; import com.redhat.rhn.domain.channel.Channel; import com.redhat.rhn.domain.channel.ChannelFactory; import com.redhat.rhn.domain.channel.ClonedChannel; import com.redhat.rhn.domain.channel.InvalidChannelRoleException; import com.redhat.rhn.domain.errata.Bug; import com.redhat.rhn.domain.errata.Cve; import com.redhat.rhn.domain.errata.CveFactory; import com.redhat.rhn.domain.errata.Errata; import com.redhat.rhn.domain.errata.ErrataFactory; import com.redhat.rhn.domain.errata.Keyword; import com.redhat.rhn.domain.org.Org; import com.redhat.rhn.domain.rhnpackage.Package; import com.redhat.rhn.domain.rhnpackage.PackageFactory; import com.redhat.rhn.domain.user.User; import com.redhat.rhn.frontend.dto.CVE; import com.redhat.rhn.frontend.xmlrpc.BaseHandler; import com.redhat.rhn.frontend.xmlrpc.DuplicateErrataException; import com.redhat.rhn.frontend.xmlrpc.InvalidAdvisoryReleaseException; import com.redhat.rhn.frontend.xmlrpc.InvalidAdvisoryTypeException; import com.redhat.rhn.frontend.xmlrpc.InvalidChannelException; import com.redhat.rhn.frontend.xmlrpc.InvalidChannelLabelException; import com.redhat.rhn.frontend.xmlrpc.InvalidErrataException; import com.redhat.rhn.frontend.xmlrpc.InvalidPackageException; import com.redhat.rhn.frontend.xmlrpc.InvalidParameterException; import com.redhat.rhn.frontend.xmlrpc.MissingErrataAttributeException; import com.redhat.rhn.frontend.xmlrpc.NoChannelsSelectedException; import com.redhat.rhn.frontend.xmlrpc.NoSuchChannelException; import com.redhat.rhn.frontend.xmlrpc.PermissionCheckFailureException; import com.redhat.rhn.frontend.xmlrpc.packages.PackageHelper; import com.redhat.rhn.manager.errata.ErrataManager; import com.redhat.rhn.manager.errata.cache.ErrataCacheManager; import com.redhat.rhn.manager.rhnpackage.PackageManager; import com.redhat.rhn.manager.user.UserManager; /** * ErrataHandler - provides methods to access errata information. * @version $Rev$ * @xmlrpc.namespace errata * @xmlrpc.doc Provides methods to access and modify errata. */ public class ErrataHandler extends BaseHandler { /** * Returns an OVAL metadata file for a given errata or CVE * @param loggedInUser The current user * @param identifier Errata identifier (either id, CVE/CAN, or Advisory name) * @return Escaped XML representing the OVAL metadata document * @throws IOException error building XML file * @throws FaultException general error occurred * * @xmlrpc.doc Retrieves the OVAL metadata associated with one or more erratas. * @xmlrpc.param #session_key() * @xmlrpc.param #param_desc("string", "identifier", "Can either be an erratum's ID, * CVE/CAN, or advisory name. In the case of CVE/CAN, all dashes must be * removed from the name.Numeric advisory IDs and advisory names * (RHSA-2006:011) can be submitted as they are.") * @xmlrpc.returntype string - The OVAL metadata document in escaped XML form. */ /** * The getOval method is being commented out due to bugzilla 504054. This bug * raises an issue of a null exception being generated on execution. The method * has been updated to address that exception; however, there is a larger issue at * hand in that the OVAL functionality is not fully supported by the application. * For example, the OVAL meta data is not synced to the database; therefore, there * will never be data to return by the method. So it is better to comment it out * than to have the method that cannot return any data. :) It is, however, * desirable to support this in the future, so we don't want to lose the logic. * public String getOval(User loggedInUser, String identifier) throws IOException, FaultException { User loggedInUser = getLoggedInUser(sessionKey); String retval = ""; List<Errata> erratas = ErrataManager.lookupErrataByIdentifier(identifier); for (Errata errata : erratas) { if (errata.getOrg() != null && !errata.getOrg().equals(loggedInUser.getOrg())) { erratas.remove(errata); } } if (erratas == null) { throw new FaultException(-1, "errataNotFound", "No erratas found for given identifier"); } List files = new LinkedList(); if (erratas != null) { for (Iterator iter = erratas.iterator(); iter.hasNext();) { Errata e = (Errata) iter.next(); List tmp = ErrataFactory.lookupErrataFilesByErrataAndFileType(e.getId(), "oval"); if ((tmp != null && tmp.size() > 0) && (e.getOrg() == null || e.getOrg().equals(loggedInUser.getOrg()))) { files.addAll(tmp); } } files = ErrataManager.resolveOvalFiles(files); if (files != null) { if (files.size() == 0) { throw new FaultException(-1, "ovalNotFound", "No OVAL files found for given errata"); } else if (files.size() == 1) { File f = (File) files.get(0); if (f != null) { InputStream in = null; byte[] buf = new byte[4096]; int readsize = 0; ByteArrayOutputStream accum = new ByteArrayOutputStream(); try { in = new FileInputStream(f); while ((readsize = in.read(buf)) > -1) { accum.write(buf, 0, readsize); } retval = new String(accum.toByteArray(), "UTF-8"); } finally { if (in != null) { in.close(); } } } } else if (files.size() > 1) { try { OvalFileAggregator agg = new OvalFileAggregator(); for (Iterator iter = files.iterator(); iter.hasNext();) { File f = (File) iter.next(); if (f != null && !f.getPath().endsWith("test-5.xml")) { agg.add(f); } } retval = StringEscapeUtils.escapeXml(agg.finish(false)); } catch (JDOMException e) { throw new FaultException(-1, "err_building_oval", e.getMessage()); } } } } return retval; } */ /** * GetDetails - Retrieves the details for a given errata. * @param loggedInUser The current user * @param advisoryName The advisory name of the errata * @return Returns a map containing the details of the errata * @throws FaultException A FaultException is thrown if the errata * corresponding to advisoryName cannot be found. * * @xmlrpc.doc Retrieves the details for the erratum matching the given * advisory name. * @xmlrpc.param #session_key() * @xmlrpc.param #param("string", "advisoryName") * @xmlrpc.returntype * #struct("erratum") * #prop("int", "id") * #prop("string", "issue_date") * #prop("string", "update_date") * #prop_desc("string", "last_modified_date", "This date is only included for * published erratum and it represents the last time the erratum was * modified.") * #prop("string", "synopsis") * #prop("int", "release") * #prop("string", "type") * #prop("string", "product") * #prop("string", "errataFrom") * #prop("string", "topic") * #prop("string", "description") * #prop("string", "references") * #prop("string", "notes") * #prop("string", "solution") * #struct_end() */ public Map<String, Object> getDetails(User loggedInUser, String advisoryName) throws FaultException { // Get the logged in user. We don't care what roles this user has, we // just want to make sure the caller is logged in. Errata errata = lookupErrataReadOnly(advisoryName, loggedInUser.getOrg()); Map<String, Object> errataMap = new HashMap<String, Object>(); errataMap.put("id", errata.getId()); if (errata.getIssueDate() != null) { errataMap.put("issue_date", LocalizationService.getInstance() .formatShortDate(errata.getIssueDate())); } if (errata.getUpdateDate() != null) { errataMap.put("update_date", LocalizationService.getInstance() .formatShortDate(errata.getUpdateDate())); } if (errata.getLastModified() != null) { errataMap.put("last_modified_date", errata.getLastModified().toString()); } if (errata.getAdvisoryRel() != null) { errataMap.put("release", errata.getAdvisoryRel()); } errataMap.put("product", StringUtils.defaultString(errata.getProduct())); errataMap.put("errataFrom", StringUtils.defaultString(errata.getErrataFrom())); errataMap.put("solution", StringUtils.defaultString(errata.getSolution())); errataMap.put("description", StringUtils.defaultString(errata.getDescription())); errataMap.put("synopsis", StringUtils.defaultString(errata.getSynopsis())); errataMap.put("topic", StringUtils.defaultString(errata.getTopic())); errataMap.put("references", StringUtils.defaultString(errata.getRefersTo())); errataMap.put("notes", StringUtils.defaultString(errata.getNotes())); errataMap.put("type", StringUtils.defaultString(errata.getAdvisoryType())); return errataMap; } /** * Set erratum details. * * @param loggedInUser The current user * @param advisoryName The advisory name of the errata * @param details Map of (optional) erratum details to be set. * @return 1 on success, exception thrown otherwise. * * @xmlrpc.doc Set erratum details. All arguments are optional and will only be modified * if included in the struct. This method will only allow for modification of custom * errata created either through the UI or API. * @xmlrpc.param #param("string", "sessionKey") * @xmlrpc.param #param("string", "advisoryName") * @xmlrpc.param * #struct("errata details") * #prop("string", "synopsis") * #prop("string", "advisory_name") * #prop("int", "advisory_release") * #prop_desc("string", "advisory_type", "Type of advisory (one of the * following: 'Security Advisory', 'Product Enhancement Advisory', * or 'Bug Fix Advisory'") * #prop("string", "product") * #prop("dateTime.iso8601", "issue_date") * #prop("dateTime.iso8601", "update_date") * #prop("string", "errataFrom") * #prop("string", "topic") * #prop("string", "description") * #prop("string", "references") * #prop("string", "notes") * #prop("string", "solution") * #prop_desc("array", "bugs", "'bugs' is the key into the struct") * #array() * #struct("bug") * #prop_desc("int", "id", "Bug Id") * #prop("string", "summary") * #prop("string", "url") * #struct_end() * #array_end() * #prop_desc("array", "keywords", "'keywords' is the key into the struct") * #array_single("string", "keyword - List of keywords to associate * with the errata.") * #prop_desc("array", "CVEs", "'cves' is the key into the struct") * #array_single("string", "cves - List of CVEs to associate * with the errata. (valid only for published errata)") * #struct_end() * * @xmlrpc.returntype #return_int_success() */ public Integer setDetails(User loggedInUser, String advisoryName, Map<String, Object> details) { Errata errata = lookupErrata(advisoryName, loggedInUser.getOrg()); if (errata.getOrg() == null) { // Errata in the null org should not be modified; therefore, this is // considered an invalid errata for this request throw new InvalidErrataException(errata.getAdvisoryName()); } // confirm that the user only provided valid keys in the map Set<String> validKeys = new HashSet<String>(); validKeys.add("synopsis"); validKeys.add("advisory_name"); validKeys.add("advisory_release"); validKeys.add("advisory_type"); validKeys.add("product"); validKeys.add("issue_date"); validKeys.add("update_date"); validKeys.add("errataFrom"); validKeys.add("topic"); validKeys.add("description"); validKeys.add("references"); validKeys.add("notes"); validKeys.add("solution"); validKeys.add("bugs"); validKeys.add("keywords"); if (errata.isPublished()) { validKeys.add("cves"); } validateMap(validKeys, details); validKeys.clear(); validKeys.add("id"); validKeys.add("summary"); validKeys.add("url"); if (details.containsKey("bugs")) { for (Map<String, Object> bugMap : (ArrayList<Map<String, Object>>) details.get("bugs")) { validateMap(validKeys, bugMap); } } if (details.containsKey("issue_date")) { try { errata.setIssueDate((Date)details.get("issue_date")); } catch (ClassCastException e) { throw new InvalidParameterException("Wrong 'issue_date' format."); } } if (details.containsKey("update_date")) { try { errata.setUpdateDate((Date)details.get("update_date")); } catch (ClassCastException e) { throw new InvalidParameterException("Wrong 'update_date' format."); } } if (details.containsKey("synopsis")) { if (StringUtils.isBlank((String)details.get("synopsis"))) { throw new InvalidParameterException("Synopsis is required."); } errata.setSynopsis((String)details.get("synopsis")); } if (details.containsKey("advisory_name")) { if (StringUtils.isBlank((String)details.get("advisory_name"))) { throw new InvalidParameterException("Advisory name is required."); } errata.setAdvisoryName((String)details.get("advisory_name")); } if (details.containsKey("advisory_release")) { Long rel = new Long((Integer)details.get("advisory_release")); if (rel.longValue() > ErrataManager.MAX_ADVISORY_RELEASE) { throw new InvalidAdvisoryReleaseException(rel.longValue()); } errata.setAdvisoryRel(rel); } if (details.containsKey("advisory_type")) { String pea = "Product Enhancement Advisory"; // hack for checkstyle if (!((String)details.get("advisory_type")).equals("Security Advisory") && !((String)details.get("advisory_type")).equals(pea) && !((String)details.get("advisory_type")).equals("Bug Fix Advisory")) { throw new InvalidParameterException("Invalid advisory type"); } errata.setAdvisoryType((String)details.get("advisory_type")); } if (details.containsKey("product")) { if (StringUtils.isBlank((String)details.get("product"))) { throw new InvalidParameterException("Product name is required."); } errata.setProduct((String)details.get("product")); } if (details.containsKey("errataFrom")) { errata.setErrataFrom((String)details.get("errataFrom")); } if (details.containsKey("topic")) { if (StringUtils.isBlank((String)details.get("topic"))) { throw new InvalidParameterException("Topic is required."); } errata.setTopic((String)details.get("topic")); } if (details.containsKey("description")) { if (StringUtils.isBlank((String)details.get("description"))) { throw new InvalidParameterException("Description is required."); } errata.setDescription((String)details.get("description")); } if (details.containsKey("solution")) { if (StringUtils.isBlank((String)details.get("solution"))) { throw new InvalidParameterException("Solution is required."); } errata.setSolution((String)details.get("solution")); } if (details.containsKey("references")) { errata.setRefersTo((String)details.get("references")); } if (details.containsKey("notes")) { errata.setNotes((String)details.get("notes")); } if (details.containsKey("bugs")) { if (errata.getBugs() != null) { errata.getBugs().clear(); HibernateFactory.getSession().flush(); } for (Map<String, Object> bugMap : (ArrayList<Map<String, Object>>) details.get("bugs")) { if (bugMap.containsKey("id") && bugMap.containsKey("summary")) { String url = null; if (bugMap.containsKey("url")) { url = (String) bugMap.get("url"); } Bug bug = ErrataFactory.createPublishedBug( new Long((Integer) bugMap.get("id")), (String) bugMap.get("summary"), url); errata.addBug(bug); } } } if (details.containsKey("keywords")) { if (errata.getKeywords() != null) { errata.getKeywords().clear(); HibernateFactory.getSession().flush(); } for (String keyword : (ArrayList<String>) details.get("keywords")) { errata.addKeyword(keyword); } } if (details.containsKey("cves")) { if (errata.getCves() != null) { errata.getCves().clear(); HibernateFactory.getSession().flush(); } for (String cveName : (ArrayList<String>) details.get("cves")) { Cve c = CveFactory.lookupByName(cveName); if (c == null) { c = new Cve(); c.setName(cveName); CveFactory.save(c); } errata.getCves().add(c); } } // ALWAYS change the advisory to match, as we do in the UI. errata.setAdvisory(errata.getAdvisoryName() + "-" + errata.getAdvisoryRel().toString()); //Save the errata ErrataManager.storeErrata(errata); return 1; } /** * ListAffectedSystems * @param loggedInUser The current user * @param advisoryName The advisory name of the errata * @return Returns an object array containing the system ids and system name * @throws FaultException A FaultException is thrown if the errata corresponding to * advisoryName cannot be found. * * @xmlrpc.doc Return the list of systems affected by the erratum with * advisory name. * @xmlrpc.param #session_key() * @xmlrpc.param #param("string", "advisoryName") * @xmlrpc.returntype * #array() * $SystemOverviewSerializer * #array_end() */ public Object[] listAffectedSystems(User loggedInUser, String advisoryName) throws FaultException { // Get the logged in user Errata errata = lookupErrata(advisoryName, loggedInUser.getOrg()); DataResult dr = ErrataManager.systemsAffectedXmlRpc(loggedInUser, errata.getId()); return dr.toArray(); } /** * Get the Bugzilla fixes for a given errata * @param loggedInUser The current user * @param advisoryName The advisory name of the errata * @return Returns a map containing the Bugzilla id and summary for each bug * @throws FaultException A FaultException is thrown if the errata * corresponding to the given advisoryName cannot be found. * * @xmlrpc.doc Get the Bugzilla fixes for an erratum matching the given * advisoryName. The bugs will be returned in a struct where the bug id is * the key. i.e. 208144="errata.bugzillaFixes Method Returns different * results than docs say" * @xmlrpc.param #session_key() * @xmlrpc.param #param("string", "advisoryName") * @xmlrpc.returntype * #struct("Bugzilla info") * #prop_desc("string", "bugzilla_id", "actual bug number is the key into the * struct") * #prop_desc("string", "bug_summary", "summary who's key is the bug id") * #struct_end() */ public Map<Long, String> bugzillaFixes(User loggedInUser, String advisoryName) throws FaultException { // Get the logged in user Errata errata = lookupErrata(advisoryName, loggedInUser.getOrg()); Set<Bug> bugs = errata.getBugs(); Map<Long, String> returnMap = new HashMap<Long, String>(); /* * Loop through and stick the bug ids and summaries into a map. This * is ok since (afaict) there isn't an unreasonable number of bugs * attatched to any erratum. */ for (Iterator<Bug> itr = bugs.iterator(); itr.hasNext();) { Bug bug = itr.next(); returnMap.put(bug.getId(), bug.getSummary()); } return returnMap; } /** * Get the keywords for a given erratum * @param loggedInUser The current user * @param advisoryName The advisory name of the erratum * @return Returns an array of keywords for the erratum * @throws FaultException A FaultException is thrown if the errata corresponding to the * given advisoryName cannot be fo * * @xmlrpc.doc Get the keywords associated with an erratum matching the * given advisory name. * @xmlrpc.param #session_key() * @xmlrpc.param #param("string", "advisoryName") * @xmlrpc.returntype #array_single("string", "Keyword associated with erratum.") */ public Object[] listKeywords(User loggedInUser, String advisoryName) throws FaultException { // Get the logged in user Errata errata = lookupErrata(advisoryName, loggedInUser.getOrg()); Set<Keyword> keywords = errata.getKeywords(); List<String> returnList = new ArrayList<String>(); for (Iterator<Keyword> itr = keywords.iterator(); itr.hasNext();) { Keyword keyword = itr.next(); returnList.add(keyword.getKeyword()); } return returnList.toArray(); } /** * Returns a list of channels (represented by a map) that the given erratum is * applicable to. * @param loggedInUser The current user * @param advisoryName The advisory name of the erratum * @return Returns an array of channels for the erratum * @throws FaultException A FaultException is thrown if the errata corresponding to the * given advisoryName cannot be found * * @xmlrpc.doc Returns a list of channels applicable to the erratum * with the given advisory name. * @xmlrpc.param #session_key() * @xmlrpc.param #param("string", "advisoryName") * @xmlrpc.returntype * #array() * #struct("channel") * #prop("int", "channel_id") * #prop("string", "label") * #prop("string", "name") * #prop("string", "parent_channel_label") * #struct_end() * #array_end() */ public Object[] applicableToChannels(User loggedInUser, String advisoryName) throws FaultException { // Get the logged in user Errata errata = lookupErrata(advisoryName, loggedInUser.getOrg()); return ErrataManager.applicableChannels(errata.getId(), loggedInUser.getOrg().getId(), null, Map.class).toArray(); } /** * Returns a list of unpublished errata for the logged-in user's Org. * @param loggedInUser The current user * @return Returns an array of errata * * @xmlrpc.doc Returns a list of unpublished errata * @xmlrpc.param #session_key() * @xmlrpc.returntype * #array() * #struct("erratum") * #prop("int", "id") * #prop("int", "published") * #prop("string", "advisory") * #prop("string", "advisory_name") * #prop("string", "advisory_type") * #prop("string", "synopsis") * #prop("dateTime.iso8601", "created") * #prop("dateTime.iso8601", "update_date") * #struct_end() * #array_end() */ public Object[] listUnpublishedErrata(User loggedInUser) { Map[] unpub = (Map[])ErrataManager.unpublishedOwnedErrata(loggedInUser, Map.class) .toArray(new Map[0]); for (Map errataItem : unpub) { // remove items that can be NULL to prevent xmlrpc failure Iterator<Map.Entry> itr = errataItem.entrySet().iterator(); for (; itr.hasNext();) { if (itr.next().getValue() == null) { itr.remove(); } } } return unpub; } /** * Returns a list of CVEs for a given erratum * @param loggedInUser The current user * @param advisoryName The advisory name of the erratum * @return Returns a list of CVEs * @throws FaultException A FaultException is thrown if the errata corresponding to the * given advisoryName cannot be found throws FaultException { * * @xmlrpc.doc Returns a list of * <a href="http://www.cve.mitre.org/" target="_blank">CVE</a>s * applicable to the erratum with the given advisory name. CVEs may be associated * only with published errata. * @xmlrpc.param #session_key() * @xmlrpc.param #param("string", "advisoryName") * @xmlrpc.returntype * #array_single("string", "cveName") * */ public List listCves(User loggedInUser, String advisoryName) throws FaultException { // Get the logged in user Errata errata = lookupErrata(advisoryName, loggedInUser.getOrg()); DataResult dr = ErrataManager.errataCVEs(errata.getId()); List returnList = new ArrayList(); //Just return the name of the cve... for (Iterator itr = dr.iterator(); itr.hasNext();) { CVE cve = (CVE) itr.next(); returnList.add(cve.getName()); } return returnList; } /** * List the packages for a given erratum * @param loggedInUser The current user * @param advisoryName The advisory name of the erratum * @return Returns an Array of maps representing a package * @throws FaultException A FaultException is thrown if the errata corresponding to the * given advisoryName cannot be found * * @xmlrpc.doc Returns a list of the packages affected by the erratum * with the given advisory name. * @xmlrpc.param #session_key() * @xmlrpc.param #param("string", "advisoryName") * @xmlrpc.returntype * #array() * #struct("package") * #prop("int", "id") * #prop("string", "name") * #prop("string", "epoch") * #prop("string", "version") * #prop("string", "release") * #prop("string", "arch_label") * #prop_array("providing_channels", "string", "- Channel label * providing this package.") * #prop("string", "build_host") * #prop("string", "description") * #prop("string", "checksum") * #prop("string", "checksum_type") * #prop("string", "vendor") * #prop("string", "summary") * #prop("string", "cookie") * #prop("string", "license") * #prop("string", "path") * #prop("string", "file") * #prop("string", "build_date") * #prop("string", "last_modified_date") * #prop("string", "size") * #prop("string", "payload_size") * #struct_end() * #array_end() */ public List<Map> listPackages(User loggedInUser, String advisoryName) throws FaultException { // Get the logged in user Errata errata = lookupErrataReadOnly(advisoryName, loggedInUser.getOrg()); List<Map> toRet = new ArrayList<Map>(); for (Iterator iter = errata.getPackages().iterator(); iter.hasNext();) { Package pkg = (Package) iter.next(); toRet.add(PackageHelper.packageToMap(pkg, loggedInUser)); } return toRet; } /** * Add a set of packages to an erratum * @param loggedInUser The current user * @param advisoryName The advisory name of the erratum * @param packageIds The ids for packages to remove * @return Returns int - representing the number of packages added, exception otherwise * @throws FaultException A FaultException is thrown if the errata corresponding to the * given advisoryName cannot be found * * @xmlrpc.doc Add a set of packages to an erratum * with the given advisory name. This method will only allow for modification * of custom errata created either through the UI or API. * @xmlrpc.param #session_key() * @xmlrpc.param #param("string", "advisoryName") * @xmlrpc.param #array_single("int", "packageId") * @xmlrpc.returntype int - representing the number of packages added, * exception otherwise */ public int addPackages(User loggedInUser, String advisoryName, List<Integer> packageIds) throws FaultException { // Get the logged in user Errata errata = lookupErrata(advisoryName, loggedInUser.getOrg()); if (errata.getOrg() == null) { // Errata in the null org should not be modified; therefore, this is // considered an invalid errata for this request throw new InvalidErrataException(errata.getAdvisoryName()); } int packagesAdded = 0; for (Integer packageId : packageIds) { Package pkg = PackageManager.lookupByIdAndUser(new Long(packageId), loggedInUser); if ((pkg != null) && (!errata.getPackages().contains(pkg))) { errata.addPackage(pkg); packagesAdded++; } } //Update Errata Cache if ((packagesAdded > 0) && errata.isPublished() && (errata.getChannels() != null)) { ErrataCacheManager.updateCacheForChannelsAsync( errata.getChannels()); } //Save the errata ErrataManager.storeErrata(errata); return packagesAdded; } /** * Remove a set of packages from an erratum * @param loggedInUser The current user * @param advisoryName The advisory name of the erratum * @param packageIds The ids for packages to remove * @return Returns int - representing the number of packages removed, * exception otherwise * @throws FaultException A FaultException is thrown if the errata corresponding to the * given advisoryName cannot be found * * @xmlrpc.doc Remove a set of packages from an erratum * with the given advisory name. This method will only allow for modification * of custom errata created either through the UI or API. * @xmlrpc.param #session_key() * @xmlrpc.param #param("string", "advisoryName") * @xmlrpc.param #array_single("int", "packageId") * @xmlrpc.returntype int - representing the number of packages removed, * exception otherwise */ public int removePackages(User loggedInUser, String advisoryName, List<Integer> packageIds) throws FaultException { // Get the logged in user Errata errata = lookupErrata(advisoryName, loggedInUser.getOrg()); if (errata.getOrg() == null) { // Errata in the null org should not be modified; therefore, this is // considered an invalid errata for this request throw new InvalidErrataException(errata.getAdvisoryName()); } int packagesRemoved = 0; for (Integer packageId : packageIds) { Package pkg = PackageManager.lookupByIdAndUser(new Long(packageId), loggedInUser); if ((pkg != null) && (errata.getPackages().contains(pkg))) { errata.removePackage(pkg); packagesRemoved++; } } //Update Errata Cache if ((packagesRemoved > 0) && errata.isPublished() && (errata.getChannels() != null)) { ErrataCacheManager.updateCacheForChannelsAsync( errata.getChannels()); } //Save the errata ErrataManager.storeErrata(errata); return packagesRemoved; } /** * Private helper method to lookup an errata and throw a Fault exception if it isn't * found * @param advisoryName The advisory name for the erratum you're looking for * @return Returns the errata or a Fault Exception * @throws FaultException Occurs when the erratum is not found */ private Errata lookupErrata(String advisoryName, Org org) throws FaultException { Errata errata = ErrataManager.lookupByAdvisory(advisoryName); /* * ErrataManager.lookupByAdvisory() could return null, so we need to check * and throw a no_such_errata exception if the errata was not found. */ if (errata == null) { throw new FaultException(-208, "no_such_errata", "The errata " + advisoryName + " cannot be found."); } /** * errata with org_id of null are public, but ones with an org id of !null are not * need to make sure here that everything is checked correclty */ if (errata.getOrg() != null && !errata.getOrg().equals(org)) { throw new FaultException(-209, "no_rights_to_access", "You don't have rights to access " + advisoryName + " errata."); } return errata; } /** * Private helper method to lookup an errata and throw a Fault exception if it isn't * found * @param advisoryName The advisory name for the erratum you're looking for * @return Returns the errata or a Fault Exception * @throws FaultException Occurs when the erratum is not found */ private Errata lookupErrataReadOnly(String advisoryName, Org org) throws FaultException { Errata errata = ErrataManager.lookupByAdvisory(advisoryName); /* * ErrataManager.lookupByAdvisory() could return null, so we need to check * and throw a no_such_errata exception if the errata was not found. */ if (errata == null) { throw new FaultException(-208, "no_such_errata", "The errata " + advisoryName + " cannot be found."); } /** * errata with org_id of null are public, but ones with an org id of !null are not * need to make sure here that everything is checked correclty */ if (errata.getOrg() == null || errata.getOrg().equals(org)) { return errata; } Set<Channel> errataChannels = errata.getChannels(); List<Channel> orgChannels = org.getAccessibleChannels(); for (Channel channel : errataChannels) { if (orgChannels.contains(channel)) { return errata; } } throw new FaultException(-209, "no_rights_to_access", "You don't have rights to access " + advisoryName + " errata."); } /** * Clones a list of errata into a specified channel * * @param loggedInUser The current user * @param channelLabel the channel's label that we are cloning into * @param advisoryNames an array of String objects containing the advisory name * of every errata you want to clone * @throws InvalidChannelRoleException if the user perms are incorrect * @return Returns an array of Errata objects, which get serialized into XMLRPC * * @xmlrpc.doc Clone a list of errata into the specified channel. * * @xmlrpc.param #session_key() * @xmlrpc.param #param("string", "channel_label") * @xmlrpc.param * #array_single("string", " advisory - The advisory name of the errata to clone.") * @xmlrpc.returntype * #array() * $ErrataSerializer * #array_end() */ public Object[] clone(User loggedInUser, String channelLabel, List advisoryNames) throws InvalidChannelRoleException { return clone(loggedInUser, channelLabel, advisoryNames, false, false); } /** * Asynchronously clones a list of errata into a specified channel * * @param loggedInUser The current user * @param channelLabel the channel's label that we are cloning into * @param advisoryNames an array of String objects containing the advisory name * of every errata you want to clone * @throws InvalidChannelRoleException if the user perms are incorrect * @return 1 on success, exception thrown otherwise. * * @xmlrpc.doc Asynchronously clone a list of errata into the specified channel. * * @xmlrpc.param #session_key() * @xmlrpc.param #param("string", "channel_label") * @xmlrpc.param * #array_single("string", " advisory - The advisory name of the errata to clone.") * @xmlrpc.returntype * #return_int_success() */ public int cloneAsync(User loggedInUser, String channelLabel, List advisoryNames) throws InvalidChannelRoleException { clone(loggedInUser, channelLabel, advisoryNames, false, true); return 1; } private Object[] clone(User loggedInUser, String channelLabel, List<String> advisoryNames, boolean inheritPackages, boolean asynchronous) { Logger log = Logger.getLogger(ErrataFactory.class); Channel channel = ChannelFactory.lookupByLabelAndUser(channelLabel, loggedInUser); if (channel == null) { throw new NoSuchChannelException(); } //if calling cloneAsOriginal, do additional checks to verify a clone if (inheritPackages) { if (!channel.isCloned()) { throw new InvalidChannelException("Cloned channel expected: " + channel.getLabel()); } Channel original = ChannelFactory.lookupOriginalChannel(channel); if (original == null) { throw new InvalidChannelException("Cannot access original " + "of the channel: " + channel.getLabel()); } // check access to the original if (ChannelFactory.lookupByIdAndUser(original.getId(), loggedInUser) == null) { throw new LookupException("User " + loggedInUser.getLogin() + " does not have access to channel " + original.getLabel()); } } if (!UserManager.verifyChannelAdmin(loggedInUser, channel)) { throw new PermissionCheckFailureException(); } List<Errata> errataToClone = new ArrayList<Errata>(); List<Long> errataIds = new ArrayList<Long>(); //We loop through once, making sure all the errata exist for (String advisory : advisoryNames) { Errata toClone = lookupErrata(advisory, loggedInUser.getOrg()); errataToClone.add(toClone); errataIds.add(toClone.getId()); } if (asynchronous) { ErrataManager.cloneErrataApiAsync(channel, errataIds, loggedInUser, inheritPackages); return new ArrayList<Errata>().toArray(); } else if (ErrataManager.channelHasPendingAsyncCloneJobs(channel)) { throw new InvalidChannelException( "Channel " + channel.getLabel() + " has pending asynchronous errata clone jobs. You must wait" + "until asychronous errata clone jobs are done."); } else { return ErrataManager.cloneErrataApi(channel, errataToClone, loggedInUser, inheritPackages); } } /** * Clones a list of errata into a specified cloned channel * according the original erratas * * @param loggedInUser The current user * @param channelLabel the cloned channel's label that we are cloning into * @param advisoryNames an array of String objects containing the advisory name * of every errata you want to clone * @throws InvalidChannelRoleException if the user perms are incorrect * @return Returns an array of Errata objects, which get serialized into XMLRPC * * @xmlrpc.doc Clones a list of errata into a specified cloned channel * according the original erratas. * * @xmlrpc.param #session_key() * @xmlrpc.param #param("string", "channel_label") * @xmlrpc.param * #array_single("string", " advisory - The advisory name of the errata to clone.") * @xmlrpc.returntype * #array() * $ErrataSerializer * #array_end() */ public Object[] cloneAsOriginal(User loggedInUser, String channelLabel, List<String> advisoryNames) throws InvalidChannelRoleException { return clone(loggedInUser, channelLabel, advisoryNames, true, false); } /** * Asynchronously clones a list of errata into a specified cloned channel * according the original erratas * * @param loggedInUser The current user * @param channelLabel the cloned channel's label that we are cloning into * @param advisoryNames an array of String objects containing the advisory name * of every errata you want to clone * @throws InvalidChannelRoleException if the user perms are incorrect * @return 1 on success, exception thrown otherwise. * * @xmlrpc.doc Asynchronously clones a list of errata into a specified cloned channel * according the original erratas * * @xmlrpc.param #session_key() * @xmlrpc.param #param("string", "channel_label") * @xmlrpc.param * #array_single("string", " advisory - The advisory name of the errata to clone.") * @xmlrpc.returntype * #return_int_success() */ public int cloneAsOriginalAsync(User loggedInUser, String channelLabel, List<String> advisoryNames) throws InvalidChannelRoleException { clone(loggedInUser, channelLabel, advisoryNames, true, true); return 1; } private Object getRequiredAttribute(Map map, String attribute) { Object value = map.get(attribute); if (value == null || StringUtils.isEmpty(value.toString())) { throw new MissingErrataAttributeException(attribute); } return value; } /** * creates an errata * @param loggedInUser The current user * @param errataInfo map containing the following values: * String "synopsis" short synopsis of the errata * String "advisory_name" advisory name of the errata * Integer "advisory_release" release number of the errata * String "advisory_type" the type of advisory for the errata (Must be one of the * following: "Security Advisory", "Product Enhancement Advisory", or * "Bug Fix Advisory" * String "product" the product the errata affects * String "errataFrom" the author of the errata * String "topic" the topic of the errata * String "description" the description of the errata * String "solution" the solution of the errata * String "references" references of the errata to be created * String "notes" notes on the errata * @param bugs a List of maps consisting of 'id' Integers and 'summary' strings * @param keywords a List of keywords for the errata * @param packageIds a List of package Id packageId Integers * @param publish should the errata be published * @param channelLabels an array of channel labels to publish to if the errata is to * be published * @throws InvalidChannelRoleException if the user perms are incorrect * @return The errata created (whether published or unpublished) * * @xmlrpc.doc Create a custom errata. If "publish" is set to true, * the errata will be published as well * @xmlrpc.param #session_key() * @xmlrpc.param * #struct("errata info") * #prop("string", "synopsis") * #prop("string", "advisory_name") * #prop("int", "advisory_release") * #prop_desc("string", "advisory_type", "Type of advisory (one of the * following: 'Security Advisory', 'Product Enhancement Advisory', * or 'Bug Fix Advisory'") * #prop("string", "product") * #prop("string", "errataFrom") * #prop("string", "topic") * #prop("string", "description") * #prop("string", "references") * #prop("string", "notes") * #prop("string", "solution") * #struct_end() * @xmlrpc.param * #array() * #struct("bug") * #prop_desc("int", "id", "Bug Id") * #prop("string", "summary") * #prop("string", "url") * #struct_end() * #array_end() * @xmlrpc.param #array_single("string", "keyword - List of keywords to associate * with the errata.") * @xmlrpc.param #array_single("int", "packageId") * @xmlrpc.param #param_desc("boolean", "publish", "Should the errata be published.") * @xmlrpc.param * #array_single("string", "channelLabel - list of channels the errata should be * published too, ignored if publish is set to false") * @xmlrpc.returntype * $ErrataSerializer */ public Errata create(User loggedInUser, Map<String, Object> errataInfo, List<Map<String, Object>> bugs, List<String> keywords, List<Integer> packageIds, boolean publish, List<String> channelLabels) throws InvalidChannelRoleException { // confirm that the user only provided valid keys in the map Set<String> validKeys = new HashSet<String>(); validKeys.add("synopsis"); validKeys.add("advisory_name"); validKeys.add("advisory_release"); validKeys.add("advisory_type"); validKeys.add("product"); validKeys.add("errataFrom"); validKeys.add("topic"); validKeys.add("description"); validKeys.add("references"); validKeys.add("notes"); validKeys.add("solution"); validateMap(validKeys, errataInfo); validKeys.clear(); validKeys.add("id"); validKeys.add("summary"); validKeys.add("url"); for (Map<String, Object> bugMap : bugs) { validateMap(validKeys, bugMap); } //Don't want them to publish an errata without any channels, //so check first before creating anything List<Channel> channels = null; if (publish) { channels = verifyChannelList(channelLabels, loggedInUser); } String synopsis = (String) getRequiredAttribute(errataInfo, "synopsis"); String advisoryName = (String) getRequiredAttribute(errataInfo, "advisory_name"); Integer advisoryRelease = (Integer) getRequiredAttribute(errataInfo, "advisory_release"); if (advisoryRelease.longValue() > ErrataManager.MAX_ADVISORY_RELEASE) { throw new InvalidAdvisoryReleaseException(advisoryRelease.longValue()); } String advisoryType = (String) getRequiredAttribute(errataInfo, "advisory_type"); String product = (String) getRequiredAttribute(errataInfo, "product"); String errataFrom = (String) errataInfo.get("errataFrom"); String topic = (String) getRequiredAttribute(errataInfo, "topic"); String description = (String) getRequiredAttribute(errataInfo, "description"); String solution = (String) getRequiredAttribute(errataInfo, "solution"); String references = (String) errataInfo.get("references"); String notes = (String) errataInfo.get("notes"); Errata newErrata = ErrataManager.lookupByAdvisory(advisoryName); if (newErrata != null) { throw new DuplicateErrataException(advisoryName); } newErrata = ErrataManager.createNewErrata(); newErrata.setOrg(loggedInUser.getOrg()); //all required newErrata.setSynopsis(synopsis); newErrata.setAdvisory(advisoryName + "-" + advisoryRelease.toString()); newErrata.setAdvisoryName(advisoryName); newErrata.setAdvisoryRel(new Long(advisoryRelease.longValue())); if (advisoryType.equals("Security Advisory") || advisoryType.equals("Product Enhancement Advisory") || advisoryType.equals("Bug Fix Advisory")) { newErrata.setAdvisoryType(advisoryType); } else { throw new InvalidAdvisoryTypeException(advisoryType); } newErrata.setProduct(product); newErrata.setTopic(topic); newErrata.setDescription(description); newErrata.setSolution(solution); newErrata.setIssueDate(new Date()); newErrata.setUpdateDate(new Date()); //not required newErrata.setErrataFrom(errataFrom); newErrata.setRefersTo(references); newErrata.setNotes(notes); for (Iterator<Map<String, Object>> itr = bugs.iterator(); itr.hasNext();) { Map<String, Object> bugMap = itr.next(); String url = null; if (bugMap.containsKey("url")) { url = (String) bugMap.get("url"); } Bug bug = ErrataFactory.createPublishedBug( new Long(((Integer)bugMap.get("id")).longValue()), (String)bugMap.get("summary"), url); newErrata.addBug(bug); } for (Iterator<String> itr = keywords.iterator(); itr.hasNext();) { String keyword = itr.next(); newErrata.addKeyword(keyword); } newErrata.setPackages(new HashSet()); for (Iterator<Integer> itr = packageIds.iterator(); itr.hasNext();) { Integer pid = itr.next(); Package pack = PackageFactory.lookupByIdAndOrg(new Long(pid.longValue()), loggedInUser.getOrg()); if (pack != null) { newErrata.addPackage(pack); } else { throw new InvalidPackageException(pid.toString()); } } ErrataFactory.save(newErrata); //if true, channels will not be null, but will be a List of channel objects if (publish) { return publish(newErrata, channels, loggedInUser, false); } return newErrata; } /** * Delete an erratum. * @param loggedInUser The current user * @param advisoryName The advisory Name of the erratum to delete * @throws FaultException if unknown or invalid erratum is provided. * @return 1 on success, exception thrown otherwise. * * @xmlrpc.doc Delete an erratum. This method will only allow for deletion * of custom errata created either through the UI or API. * @xmlrpc.param #session_key() * @xmlrpc.param #param("string", "advisoryName") * @xmlrpc.returntype #return_int_success() */ public Integer delete(User loggedInUser, String advisoryName) throws FaultException { Errata errata = lookupErrata(advisoryName, loggedInUser.getOrg()); if (errata.getOrg() == null) { // Errata in the null org should not be modified; therefore, this is // considered an invalid errata for this request throw new InvalidErrataException(errata.getAdvisoryName()); } ErrataManager.deleteErratum(loggedInUser, errata); return 1; } /** * Publishes an existing (unpublished) errata to a set of channels * @param loggedInUser The current user * @param advisory The advisory Name of the errata to publish * @param channelLabels List of channels to publish the errata to * @throws InvalidChannelRoleException if the user perms are incorrect * @return the published errata * * @xmlrpc.doc Publish an existing (unpublished) errata to a set of channels. * @xmlrpc.param #session_key() * @xmlrpc.param #param("string", "advisoryName") * @xmlrpc.param * #array_single("string", "channelLabel - list of channel labels to publish to") * @xmlrpc.returntype * $ErrataSerializer */ public Errata publish(User loggedInUser, String advisory, List<String> channelLabels) throws InvalidChannelRoleException { List<Channel> channels = verifyChannelList(channelLabels, loggedInUser); Errata toPublish = lookupErrata(advisory, loggedInUser.getOrg()); return publish(toPublish, channels, loggedInUser, false); } /** * Publishes an existing (unpublished) cloned errata to a set of cloned channels * according to its original erratum * @param loggedInUser The current user * @param advisory The advisory Name of the errata to publish * @param channelLabels List of channels to publish the errata to * @throws InvalidChannelRoleException if the user perms are incorrect * @return the published errata * * @xmlrpc.doc Publishes an existing (unpublished) cloned errata to a set of cloned * channels according to its original erratum * @xmlrpc.param #session_key() * @xmlrpc.param #param("string", "advisoryName") * @xmlrpc.param * #array_single("string", "channelLabel - list of channel labels to publish to") * @xmlrpc.returntype * $ErrataSerializer */ public Errata publishAsOriginal(User loggedInUser, String advisory, List<String> channelLabels) throws InvalidChannelRoleException { List<Channel> channels = verifyChannelList(channelLabels, loggedInUser); for (Channel c : channels) { ClonedChannel cc = null; try { cc = (ClonedChannel) c; } catch (ClassCastException e) { // just catch, do not do anything } finally { if (cc == null || !cc.isCloned()) { throw new InvalidChannelException("Cloned channel " + "expected: " + c.getLabel()); } } Channel original = ChannelFactory.lookupOriginalChannel(c); if (original == null) { throw new InvalidChannelException("Cannot access original " + "of the channel: " + c.getLabel()); } // check access to the original if (ChannelFactory.lookupByIdAndUser(original.getId(), loggedInUser) == null) { throw new LookupException("User " + loggedInUser.getLogin() + " does not have access to channel " + original.getLabel()); } } Errata toPublish = lookupErrata(advisory, loggedInUser.getOrg()); if (!toPublish.isCloned()) { throw new InvalidErrataException("Cloned errata expected."); } return publish(toPublish, channels, loggedInUser, true); } /** * Verify a list of channels labels, and populate their corresponding * Channel objects into a List. This is primarily used before publishing * to verify all channels are valid before starting the errata creation * @param channelsLabels the List of channel labels to verify * @param org the org of the user * @return a List of channel objects */ private List<Channel> verifyChannelList(List<String> channelsLabels, User user) { if (channelsLabels.size() == 0) { throw new NoChannelsSelectedException(); } List<Channel> resolvedList = new ArrayList<Channel>(); for (Iterator<String> itr = channelsLabels.iterator(); itr.hasNext();) { String channelLabel = itr.next(); Channel channel = ChannelFactory.lookupByLabelAndUser(channelLabel, user); if (channel == null) { throw new InvalidChannelLabelException(); } if (!UserManager.verifyChannelAdmin(user, channel)) { throw new PermissionCheckFailureException(); } resolvedList.add(channel); } return resolvedList; } /** * private helper method to publish the errata * @param errata the Unpublished errata to publish * @param channels A list of channel objects * @return The published Errata */ private Errata publish(Errata errata, List<Channel> channels, User user, boolean inheritPackages) { Errata published = ErrataFactory.publish(errata); for (Channel chan : channels) { List<Errata> list = new ArrayList<Errata>(); list.add(published); published = ErrataFactory.publishToChannel(list, chan, user, inheritPackages).get(0); } return published; } /** * list errata by date * @param loggedInUser The current user * @param channelLabel channel associated with the errata you are interested in. * @return List of Errata objects * @deprecated being replaced by channel.software.listErrata(User LoggedInUser, * string channelLabel) * * @xmlrpc.doc List errata that have been applied to a particular channel by date. * @xmlrpc.param #session_key() * @xmlrpc.param #param("string", "channelLabel") * @xmlrpc.returntype * #array() * $ErrataSerializer * #array_end() */ @Deprecated public List listByDate(User loggedInUser, String channelLabel) { Channel channel = ChannelFactory.lookupByLabel(loggedInUser.getOrg(), channelLabel); return ErrataFactory.lookupByChannelSorted(loggedInUser.getOrg(), channel); } /** * Lookup the details for errata associated with the given CVE. * @param loggedInUser The current user * @param cveName name of the CVE * @return List of Errata objects * * @xmlrpc.doc Lookup the details for errata associated with the given CVE * (e.g. CVE-2008-3270) * @xmlrpc.param #session_key() * @xmlrpc.param #param("string", "cveName") * @xmlrpc.returntype * #array() * $ErrataSerializer * #array_end() */ public List<Errata> findByCve(User loggedInUser, String cveName) { // Get the logged in user. We don't care what roles this user has, we // just want to make sure the caller is logged in. List<Errata> erratas = ErrataManager.lookupByCVE(cveName); for (Iterator<Errata> iter = erratas.iterator(); iter.hasNext();) { Errata errata = iter.next(); // Remove errata that do not apply to the user's org if (errata.getOrg() != null && !errata.getOrg().equals(loggedInUser.getOrg())) { iter.remove(); } } return erratas; } }