001/*
002 * Portions of this software was developed by employees of the National Institute
003 * of Standards and Technology (NIST), an agency of the Federal Government and is
004 * being made available as a public service. Pursuant to title 17 United States
005 * Code Section 105, works of NIST employees are not subject to copyright
006 * protection in the United States. This software may be subject to foreign
007 * copyright. Permission in the United States and in foreign countries, to the
008 * extent that NIST may hold copyright, to use, copy, modify, create derivative
009 * works, and distribute this software and its documentation without fee is hereby
010 * granted on a non-exclusive basis, provided that this notice and disclaimer
011 * of warranty appears in all copies.
012 *
013 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
014 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
015 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
016 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
017 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
018 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
019 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
020 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
021 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
022 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
023 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
024 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
025 */
026
027package gov.nist.secauto.oscal.lib.profile.resolver.selection;
028
029import com.fasterxml.jackson.core.Version;
030import com.fasterxml.jackson.core.util.VersionUtil;
031
032import gov.nist.secauto.metaschema.model.common.metapath.item.IDocumentNodeItem;
033import gov.nist.secauto.metaschema.model.common.metapath.item.IModelNodeItem;
034import gov.nist.secauto.metaschema.model.common.util.CollectionUtil;
035import gov.nist.secauto.metaschema.model.common.util.ObjectUtils;
036import gov.nist.secauto.oscal.lib.model.BackMatter;
037import gov.nist.secauto.oscal.lib.model.BackMatter.Resource;
038import gov.nist.secauto.oscal.lib.model.Catalog;
039import gov.nist.secauto.oscal.lib.model.CatalogGroup;
040import gov.nist.secauto.oscal.lib.model.Control;
041import gov.nist.secauto.oscal.lib.model.Metadata;
042import gov.nist.secauto.oscal.lib.model.Parameter;
043import gov.nist.secauto.oscal.lib.model.ProfileImport;
044import gov.nist.secauto.oscal.lib.profile.resolver.ProfileResolutionEvaluationException;
045import gov.nist.secauto.oscal.lib.profile.resolver.ProfileResolutionException;
046import gov.nist.secauto.oscal.lib.profile.resolver.policy.ReferenceCountingVisitor;
047import gov.nist.secauto.oscal.lib.profile.resolver.support.BasicIndexer;
048import gov.nist.secauto.oscal.lib.profile.resolver.support.IEntityItem;
049import gov.nist.secauto.oscal.lib.profile.resolver.support.IIndexer;
050
051import java.net.URI;
052import java.util.LinkedList;
053import java.util.List;
054import java.util.stream.Collectors;
055
056import edu.umd.cs.findbugs.annotations.NonNull;
057
058public class Import {
059
060  @NonNull
061  private final IDocumentNodeItem profileDocument;
062  @NonNull
063  private final IModelNodeItem profileImportItem;
064
065  public Import(
066      @NonNull IDocumentNodeItem profileDocument,
067      @NonNull IModelNodeItem profileImportItem) {
068
069    this.profileDocument = profileDocument;
070    this.profileImportItem = profileImportItem;
071  }
072
073  protected IDocumentNodeItem getProfileItem() {
074    return profileDocument;
075  }
076
077  protected IModelNodeItem getProfileImportItem() {
078    return profileImportItem;
079  }
080
081  @NonNull
082  protected ProfileImport getProfileImport() {
083    return ObjectUtils.requireNonNull((ProfileImport) profileImportItem.getValue());
084  }
085
086  private static Catalog toCatalog(IDocumentNodeItem catalogDocument) {
087    return (Catalog) catalogDocument.getValue();
088  }
089
090  @NonNull
091  protected IControlFilter newControlFilter() {
092    return IControlFilter.newInstance(getProfileImport());
093  }
094
095  @NonNull
096  protected IIndexer newIndexer() {
097    // TODO: add support for reassignment
098    // IIdentifierMapper mapper = IIdentifierMapper.IDENTITY;
099    // IIndexer indexer = new ReassignmentIndexer(mapper);
100    return new BasicIndexer();
101  }
102
103  @NonNull
104  public IIndexer resolve(@NonNull IDocumentNodeItem importedCatalogDocument, @NonNull Catalog resolvedCatalog)
105      throws ProfileResolutionException {
106    ProfileImport profileImport = getProfileImport();
107    URI uri = ObjectUtils.requireNonNull(profileImport.getHref(), "profile import href is null");
108
109    // determine which controls and groups to keep
110    IControlFilter filter = newControlFilter();
111    IIndexer indexer = newIndexer();
112    IControlSelectionState state = new ControlSelectionState(indexer, filter);
113
114    try {
115      ControlSelectionVisitor.instance().visitCatalog(importedCatalogDocument, state);
116
117      // process references
118      ReferenceCountingVisitor.instance().visitCatalog(importedCatalogDocument, indexer, uri);
119
120      // filter based on selections
121      FilterNonSelectedVisitor.instance().visitCatalog(importedCatalogDocument, indexer);
122    } catch (ProfileResolutionEvaluationException ex) {
123      throw new ProfileResolutionException(
124          String.format("Unable to resolve profile import '%s'. %s", uri.toString(), ex.getMessage()), ex);
125    }
126
127    Catalog importedCatalog = toCatalog(importedCatalogDocument);
128    for (Parameter param : CollectionUtil.listOrEmpty(importedCatalog.getParams())) {
129      if (param != null) {
130        resolvedCatalog.addParam(param);
131      }
132    }
133    for (Control control : CollectionUtil.listOrEmpty(importedCatalog.getControls())) {
134      if (control != null) {
135        resolvedCatalog.addControl(control);
136      }
137    }
138    for (CatalogGroup group : CollectionUtil.listOrEmpty(importedCatalog.getGroups())) {
139      if (group != null) {
140        resolvedCatalog.addGroup(group);
141      }
142    }
143
144    generateMetadata(importedCatalogDocument, resolvedCatalog, indexer);
145    generateBackMatter(importedCatalogDocument, resolvedCatalog, indexer);
146    return indexer;
147  }
148
149  private static void generateMetadata(
150      @NonNull IDocumentNodeItem importedCatalogDocument,
151      @NonNull Catalog resolvedCatalog,
152      @NonNull IIndexer indexer) {
153    Metadata importedMetadata = toCatalog(importedCatalogDocument).getMetadata();
154
155    if (importedMetadata != null) {
156      Metadata resolvedMetadata = resolvedCatalog.getMetadata();
157      if (resolvedMetadata == null) {
158        resolvedMetadata = new Metadata();
159        resolvedCatalog.setMetadata(resolvedMetadata);
160      }
161      resolveMetadata(importedMetadata, resolvedMetadata, indexer);
162    }
163  }
164
165  private static void resolveMetadata(
166      @NonNull Metadata imported,
167      @NonNull Metadata resolved,
168      @NonNull IIndexer indexer) {
169    String importedVersion = imported.getOscalVersion();
170    if (importedVersion != null) {
171      Version importOscalVersion = VersionUtil.parseVersion(importedVersion, null, null);
172
173      Version resolvedCatalogVersion
174          = VersionUtil.parseVersion(resolved.getOscalVersion(), null, null);
175
176      if (importOscalVersion.compareTo(resolvedCatalogVersion) > 0) {
177        resolved.setOscalVersion(importOscalVersion.toString());
178      }
179    }
180
181    // copy roles, parties, and locations with prop name:keep and any referenced
182    resolved.setRoles(
183        IIndexer.filterDistinct(
184            ObjectUtils.notNull(CollectionUtil.listOrEmpty(resolved.getRoles()).stream()),
185            indexer.getEntitiesByItemType(IEntityItem.ItemType.ROLE),
186            item -> item.getId())
187            .collect(Collectors.toCollection(LinkedList::new)));
188    resolved.setParties(
189        IIndexer.filterDistinct(
190            ObjectUtils.notNull(CollectionUtil.listOrEmpty(resolved.getParties()).stream()),
191            indexer.getEntitiesByItemType(IEntityItem.ItemType.PARTY),
192            item -> item.getUuid())
193            .collect(Collectors.toCollection(LinkedList::new)));
194    resolved.setLocations(
195        IIndexer.filterDistinct(
196            ObjectUtils.notNull(CollectionUtil.listOrEmpty(resolved.getLocations()).stream()),
197            indexer.getEntitiesByItemType(IEntityItem.ItemType.LOCATION),
198            item -> item.getUuid())
199            .collect(Collectors.toCollection(LinkedList::new)));
200  }
201
202  @SuppressWarnings("PMD.AvoidDeeplyNestedIfStmts") // not worth a function call
203  private static void generateBackMatter(
204      @NonNull IDocumentNodeItem importedCatalogDocument,
205      @NonNull Catalog resolvedCatalog,
206      @NonNull IIndexer indexer) {
207    BackMatter importedBackMatter = toCatalog(importedCatalogDocument).getBackMatter();
208
209    if (importedBackMatter != null) {
210      BackMatter resolvedBackMatter = resolvedCatalog.getBackMatter();
211
212      List<Resource> resolvedResources = resolvedBackMatter == null ? CollectionUtil.emptyList()
213          : CollectionUtil.listOrEmpty(resolvedBackMatter.getResources());
214
215      List<Resource> resources = IIndexer.filterDistinct(
216          ObjectUtils.notNull(resolvedResources.stream()),
217          indexer.getEntitiesByItemType(IEntityItem.ItemType.RESOURCE),
218          item -> item.getUuid())
219          .collect(Collectors.toCollection(LinkedList::new));
220
221      if (!resources.isEmpty()) {
222        if (resolvedBackMatter == null) {
223          resolvedBackMatter = new BackMatter();
224          resolvedCatalog.setBackMatter(resolvedBackMatter);
225        }
226
227        resolvedBackMatter.setResources(resources);
228      }
229    }
230  }
231}