1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.oscal.lib.profile.resolver.selection;
7   
8   import com.fasterxml.jackson.core.Version;
9   import com.fasterxml.jackson.core.util.VersionUtil;
10  
11  import gov.nist.secauto.metaschema.core.metapath.item.node.IAssemblyNodeItem;
12  import gov.nist.secauto.metaschema.core.metapath.item.node.IDocumentNodeItem;
13  import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem;
14  import gov.nist.secauto.metaschema.core.metapath.item.node.IRootAssemblyNodeItem;
15  import gov.nist.secauto.metaschema.core.util.CollectionUtil;
16  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
17  import gov.nist.secauto.oscal.lib.model.BackMatter;
18  import gov.nist.secauto.oscal.lib.model.BackMatter.Resource;
19  import gov.nist.secauto.oscal.lib.model.Catalog;
20  import gov.nist.secauto.oscal.lib.model.CatalogGroup;
21  import gov.nist.secauto.oscal.lib.model.Control;
22  import gov.nist.secauto.oscal.lib.model.Metadata;
23  import gov.nist.secauto.oscal.lib.model.Metadata.Location;
24  import gov.nist.secauto.oscal.lib.model.Metadata.Party;
25  import gov.nist.secauto.oscal.lib.model.Metadata.Role;
26  import gov.nist.secauto.oscal.lib.model.Parameter;
27  import gov.nist.secauto.oscal.lib.model.ProfileImport;
28  import gov.nist.secauto.oscal.lib.profile.resolver.ProfileResolutionEvaluationException;
29  import gov.nist.secauto.oscal.lib.profile.resolver.ProfileResolutionException;
30  import gov.nist.secauto.oscal.lib.profile.resolver.ProfileResolver.UriResolver;
31  import gov.nist.secauto.oscal.lib.profile.resolver.policy.ReferenceCountingVisitor;
32  import gov.nist.secauto.oscal.lib.profile.resolver.support.BasicIndexer;
33  import gov.nist.secauto.oscal.lib.profile.resolver.support.IEntityItem;
34  import gov.nist.secauto.oscal.lib.profile.resolver.support.IIndexer;
35  import gov.nist.secauto.oscal.lib.profile.resolver.support.IIndexer.SelectionStatus;
36  
37  import java.net.URI;
38  import java.util.LinkedList;
39  import java.util.List;
40  import java.util.stream.Collectors;
41  
42  import edu.umd.cs.findbugs.annotations.NonNull;
43  
44  public class Import {
45  
46    @NonNull
47    private final IRootAssemblyNodeItem profile;
48    @NonNull
49    private final IAssemblyNodeItem profileImportItem;
50  
51    public Import(
52        @NonNull IRootAssemblyNodeItem profile,
53        @NonNull IAssemblyNodeItem profileImportItem) {
54  
55      this.profile = profile;
56      this.profileImportItem = profileImportItem;
57    }
58  
59    protected IRootAssemblyNodeItem getProfileItem() {
60      return profile;
61    }
62  
63    protected IAssemblyNodeItem getProfileImportItem() {
64      return profileImportItem;
65    }
66  
67    @NonNull
68    protected ProfileImport getProfileImport() {
69      return ObjectUtils.requireNonNull((ProfileImport) profileImportItem.getValue());
70    }
71  
72    private static Catalog toCatalog(@NonNull IDocumentNodeItem catalogDocument) {
73      return (Catalog) INodeItem.toValue(catalogDocument);
74    }
75  
76    @NonNull
77    protected IControlFilter newControlFilter() {
78      return IControlFilter.newInstance(getProfileImport());
79    }
80  
81    @NonNull
82    protected IIndexer newIndexer() {
83      // TODO: add support for reassignment
84      // IIdentifierMapper mapper = IIdentifierMapper.IDENTITY;
85      // IIndexer indexer = new ReassignmentIndexer(mapper);
86      return new BasicIndexer();
87    }
88  
89    @NonNull
90    public IIndexer resolve(
91        @NonNull IDocumentNodeItem importedCatalogDocument,
92        @NonNull Catalog resolvedCatalog,
93        @NonNull UriResolver uriResolver)
94        throws ProfileResolutionException {
95      ProfileImport profileImport = getProfileImport();
96      URI uri = ObjectUtils.requireNonNull(profileImport.getHref(), "profile import href is null");
97  
98      // determine which controls and groups to keep
99      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 }