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