001/*
002 * SPDX-FileCopyrightText: none
003 * SPDX-License-Identifier: CC0-1.0
004 */
005
006package gov.nist.secauto.oscal.lib.profile.resolver.selection;
007
008import com.fasterxml.jackson.core.Version;
009import com.fasterxml.jackson.core.util.VersionUtil;
010
011import gov.nist.secauto.metaschema.core.metapath.item.node.IAssemblyNodeItem;
012import gov.nist.secauto.metaschema.core.metapath.item.node.IDocumentNodeItem;
013import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem;
014import gov.nist.secauto.metaschema.core.metapath.item.node.IRootAssemblyNodeItem;
015import gov.nist.secauto.metaschema.core.util.CollectionUtil;
016import gov.nist.secauto.metaschema.core.util.ObjectUtils;
017import gov.nist.secauto.oscal.lib.model.BackMatter;
018import gov.nist.secauto.oscal.lib.model.BackMatter.Resource;
019import gov.nist.secauto.oscal.lib.model.Catalog;
020import gov.nist.secauto.oscal.lib.model.CatalogGroup;
021import gov.nist.secauto.oscal.lib.model.Control;
022import gov.nist.secauto.oscal.lib.model.Metadata;
023import gov.nist.secauto.oscal.lib.model.Metadata.Location;
024import gov.nist.secauto.oscal.lib.model.Metadata.Party;
025import gov.nist.secauto.oscal.lib.model.Metadata.Role;
026import gov.nist.secauto.oscal.lib.model.Parameter;
027import gov.nist.secauto.oscal.lib.model.ProfileImport;
028import gov.nist.secauto.oscal.lib.profile.resolver.ProfileResolutionEvaluationException;
029import gov.nist.secauto.oscal.lib.profile.resolver.ProfileResolutionException;
030import gov.nist.secauto.oscal.lib.profile.resolver.ProfileResolver.UriResolver;
031import gov.nist.secauto.oscal.lib.profile.resolver.policy.ReferenceCountingVisitor;
032import gov.nist.secauto.oscal.lib.profile.resolver.support.BasicIndexer;
033import gov.nist.secauto.oscal.lib.profile.resolver.support.IEntityItem;
034import gov.nist.secauto.oscal.lib.profile.resolver.support.IIndexer;
035import gov.nist.secauto.oscal.lib.profile.resolver.support.IIndexer.SelectionStatus;
036
037import java.net.URI;
038import java.util.LinkedList;
039import java.util.List;
040import java.util.stream.Collectors;
041
042import edu.umd.cs.findbugs.annotations.NonNull;
043
044public class Import {
045
046  @NonNull
047  private final IRootAssemblyNodeItem profile;
048  @NonNull
049  private final IAssemblyNodeItem profileImportItem;
050
051  public Import(
052      @NonNull IRootAssemblyNodeItem profile,
053      @NonNull IAssemblyNodeItem profileImportItem) {
054
055    this.profile = profile;
056    this.profileImportItem = profileImportItem;
057  }
058
059  protected IRootAssemblyNodeItem getProfileItem() {
060    return profile;
061  }
062
063  protected IAssemblyNodeItem getProfileImportItem() {
064    return profileImportItem;
065  }
066
067  @NonNull
068  protected ProfileImport getProfileImport() {
069    return ObjectUtils.requireNonNull((ProfileImport) profileImportItem.getValue());
070  }
071
072  private static Catalog toCatalog(@NonNull IDocumentNodeItem catalogDocument) {
073    return (Catalog) INodeItem.toValue(catalogDocument);
074  }
075
076  @NonNull
077  protected IControlFilter newControlFilter() {
078    return IControlFilter.newInstance(getProfileImport());
079  }
080
081  @NonNull
082  protected IIndexer newIndexer() {
083    // TODO: add support for reassignment
084    // IIdentifierMapper mapper = IIdentifierMapper.IDENTITY;
085    // IIndexer indexer = new ReassignmentIndexer(mapper);
086    return new BasicIndexer();
087  }
088
089  @NonNull
090  public IIndexer resolve(
091      @NonNull IDocumentNodeItem importedCatalogDocument,
092      @NonNull Catalog resolvedCatalog,
093      @NonNull UriResolver uriResolver)
094      throws ProfileResolutionException {
095    ProfileImport profileImport = getProfileImport();
096    URI uri = ObjectUtils.requireNonNull(profileImport.getHref(), "profile import href is null");
097
098    // determine which controls and groups to keep
099    IControlFilter filter = newControlFilter();
100    IIndexer indexer = newIndexer();
101    // the catalog is always selected. This ensures that controls defined at the
102    // catalog level are kept and not duplicated
103    indexer.setSelectionStatus(importedCatalogDocument.getRootAssemblyNodeItem(), SelectionStatus.SELECTED);
104    IControlSelectionState state = new ControlSelectionState(indexer, filter);
105
106    try {
107      ControlSelectionVisitor.instance().visitCatalog(importedCatalogDocument, state);
108
109      // process references
110      ReferenceCountingVisitor.instance().visitCatalog(importedCatalogDocument, indexer, uriResolver);
111
112      // filter based on selections
113      FilterNonSelectedVisitor.instance().visitCatalog(importedCatalogDocument, indexer);
114    } catch (ProfileResolutionEvaluationException ex) {
115      throw new ProfileResolutionException(
116          String.format("Import: Unable to resolve profile import '%s'. %s", uri.toString(), ex.getMessage()), ex);
117    }
118
119    Catalog importedCatalog = toCatalog(importedCatalogDocument);
120    for (Parameter param : CollectionUtil.listOrEmpty(importedCatalog.getParams())) {
121      if (param != null) {
122        resolvedCatalog.addParam(param);
123      }
124    }
125    for (Control control : CollectionUtil.listOrEmpty(importedCatalog.getControls())) {
126      if (control != null) {
127        resolvedCatalog.addControl(control);
128      }
129    }
130    for (CatalogGroup group : CollectionUtil.listOrEmpty(importedCatalog.getGroups())) {
131      if (group != null) {
132        resolvedCatalog.addGroup(group);
133      }
134    }
135
136    generateMetadata(importedCatalogDocument, resolvedCatalog, indexer);
137    generateBackMatter(importedCatalogDocument, resolvedCatalog, indexer);
138    return indexer;
139  }
140
141  private static void generateMetadata(
142      @NonNull IDocumentNodeItem importedCatalogDocument,
143      @NonNull Catalog resolvedCatalog,
144      @NonNull IIndexer indexer) {
145    Metadata importedMetadata = toCatalog(importedCatalogDocument).getMetadata();
146
147    if (importedMetadata != null) {
148      Metadata resolvedMetadata = resolvedCatalog.getMetadata();
149      if (resolvedMetadata == null) {
150        resolvedMetadata = new Metadata();
151        resolvedCatalog.setMetadata(resolvedMetadata);
152      }
153      resolveMetadata(importedMetadata, resolvedMetadata, indexer);
154    }
155  }
156
157  private static void resolveMetadata(
158      @NonNull Metadata imported,
159      @NonNull Metadata resolved,
160      @NonNull IIndexer indexer) {
161    String importedVersion = imported.getOscalVersion();
162    if (importedVersion != null) {
163      Version importOscalVersion = VersionUtil.parseVersion(importedVersion, null, null);
164
165      Version resolvedCatalogVersion
166          = VersionUtil.parseVersion(resolved.getOscalVersion(), null, null);
167
168      if (importOscalVersion.compareTo(resolvedCatalogVersion) > 0) {
169        resolved.setOscalVersion(importOscalVersion.toString());
170      }
171    }
172
173    // copy roles, parties, and locations with prop name:keep and any referenced
174    resolved.setRoles(
175        IIndexer.filterDistinct(
176            ObjectUtils.notNull(CollectionUtil.listOrEmpty(resolved.getRoles()).stream()),
177            indexer.getEntitiesByItemType(IEntityItem.ItemType.ROLE),
178            Role::getId)
179            .collect(Collectors.toCollection(LinkedList::new)));
180    resolved.setParties(
181        IIndexer.filterDistinct(
182            ObjectUtils.notNull(CollectionUtil.listOrEmpty(resolved.getParties()).stream()),
183            indexer.getEntitiesByItemType(IEntityItem.ItemType.PARTY),
184            Party::getUuid)
185            .collect(Collectors.toCollection(LinkedList::new)));
186    resolved.setLocations(
187        IIndexer.filterDistinct(
188            ObjectUtils.notNull(CollectionUtil.listOrEmpty(resolved.getLocations()).stream()),
189            indexer.getEntitiesByItemType(IEntityItem.ItemType.LOCATION),
190            Location::getUuid)
191            .collect(Collectors.toCollection(LinkedList::new)));
192  }
193
194  @SuppressWarnings("PMD.AvoidDeeplyNestedIfStmts") // not worth a function call
195  private static void generateBackMatter(
196      @NonNull IDocumentNodeItem importedCatalogDocument,
197      @NonNull Catalog resolvedCatalog,
198      @NonNull IIndexer indexer) {
199    BackMatter importedBackMatter = toCatalog(importedCatalogDocument).getBackMatter();
200
201    if (importedBackMatter != null) {
202      BackMatter resolvedBackMatter = resolvedCatalog.getBackMatter();
203
204      List<Resource> resolvedResources = resolvedBackMatter == null ? CollectionUtil.emptyList()
205          : CollectionUtil.listOrEmpty(resolvedBackMatter.getResources());
206
207      List<Resource> resources = IIndexer.filterDistinct(
208          ObjectUtils.notNull(resolvedResources.stream()),
209          indexer.getEntitiesByItemType(IEntityItem.ItemType.RESOURCE),
210          Resource::getUuid)
211          .collect(Collectors.toCollection(LinkedList::new));
212
213      if (!resources.isEmpty()) {
214        if (resolvedBackMatter == null) {
215          resolvedBackMatter = new BackMatter();
216          resolvedCatalog.setBackMatter(resolvedBackMatter);
217        }
218
219        resolvedBackMatter.setResources(resources);
220      }
221    }
222  }
223}