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.policy.ReferenceCountingVisitor;
31  import gov.nist.secauto.oscal.lib.profile.resolver.support.BasicIndexer;
32  import gov.nist.secauto.oscal.lib.profile.resolver.support.IEntityItem;
33  import gov.nist.secauto.oscal.lib.profile.resolver.support.IIndexer;
34  
35  import java.net.URI;
36  import java.util.LinkedList;
37  import java.util.List;
38  import java.util.stream.Collectors;
39  
40  import edu.umd.cs.findbugs.annotations.NonNull;
41  
42  public class Import {
43  
44    @NonNull
45    private final IRootAssemblyNodeItem profile;
46    @NonNull
47    private final IAssemblyNodeItem profileImportItem;
48  
49    public Import(
50        @NonNull IRootAssemblyNodeItem profile,
51        @NonNull IAssemblyNodeItem profileImportItem) {
52  
53      this.profile = profile;
54      this.profileImportItem = profileImportItem;
55    }
56  
57    protected IRootAssemblyNodeItem getProfileItem() {
58      return profile;
59    }
60  
61    protected IAssemblyNodeItem getProfileImportItem() {
62      return profileImportItem;
63    }
64  
65    @NonNull
66    protected ProfileImport getProfileImport() {
67      return ObjectUtils.requireNonNull((ProfileImport) profileImportItem.getValue());
68    }
69  
70    private static Catalog toCatalog(@NonNull IDocumentNodeItem catalogDocument) {
71      return (Catalog) INodeItem.toValue(catalogDocument);
72    }
73  
74    @NonNull
75    protected IControlFilter newControlFilter() {
76      return IControlFilter.newInstance(getProfileImport());
77    }
78  
79    @NonNull
80    protected IIndexer newIndexer() {
81      // TODO: add support for reassignment
82      // IIdentifierMapper mapper = IIdentifierMapper.IDENTITY;
83      // IIndexer indexer = new ReassignmentIndexer(mapper);
84      return new BasicIndexer();
85    }
86  
87    @NonNull
88    public IIndexer resolve(@NonNull IDocumentNodeItem importedCatalogDocument, @NonNull Catalog resolvedCatalog)
89        throws ProfileResolutionException {
90      ProfileImport profileImport = getProfileImport();
91      URI uri = ObjectUtils.requireNonNull(profileImport.getHref(), "profile import href is null");
92  
93      // determine which controls and groups to keep
94      IControlFilter filter = newControlFilter();
95      IIndexer indexer = newIndexer();
96      IControlSelectionState state = new ControlSelectionState(indexer, filter);
97  
98      try {
99        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 }