1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package gov.nist.secauto.oscal.lib.profile.resolver.selection;
28
29 import gov.nist.secauto.metaschema.model.common.util.ObjectUtils;
30 import gov.nist.secauto.oscal.lib.model.Matching;
31 import gov.nist.secauto.oscal.lib.model.control.catalog.IControl;
32 import gov.nist.secauto.oscal.lib.model.control.profile.IProfileSelectControlById;
33 import gov.nist.secauto.oscal.lib.profile.resolver.ProfileResolutionEvaluationException;
34
35 import org.apache.commons.lang3.tuple.Pair;
36 import org.apache.logging.log4j.LogManager;
37 import org.apache.logging.log4j.Logger;
38
39 import java.util.Collections;
40 import java.util.List;
41 import java.util.Objects;
42 import java.util.Set;
43 import java.util.regex.Pattern;
44 import java.util.stream.Collectors;
45
46 import edu.umd.cs.findbugs.annotations.NonNull;
47
48 public class DefaultControlSelectionFilter implements IControlSelectionFilter {
49 private static final Logger LOGGER = LogManager.getLogger(DefaultControlSelectionFilter.class);
50
51 @NonNull
52 private final List<Selection> selections;
53
54
55
56
57
58
59
60
61 @SuppressWarnings("null")
62 public DefaultControlSelectionFilter(@NonNull List<? extends IProfileSelectControlById> selections) {
63 this.selections = selections.stream()
64
65 .filter(Objects::nonNull)
66
67 .map(selection -> new Selection(selection))
68 .collect(Collectors.toUnmodifiableList());
69 }
70
71 @NonNull
72 @Override
73 public Pair<Boolean, Boolean> apply(IControl control) {
74 String id = control.getId();
75 if (id == null) {
76 throw new ProfileResolutionEvaluationException("control is missing an identifier");
77 }
78 return match(id);
79 }
80
81
82
83
84
85
86
87
88
89
90
91 @SuppressWarnings("null")
92 @NonNull
93 protected Pair<Boolean, Boolean> match(String id) {
94 return selections.parallelStream()
95 .map(selection -> selection.match(id))
96
97 .filter(pair -> pair.getLeft())
98
99 .reduce((first, second) -> {
100 Pair<Boolean, Boolean> result;
101 if (first.getLeft() || second.getLeft()) {
102
103 boolean withChild = first.getLeft() && first.getRight() || second.getLeft() && second.getRight();
104 result = Pair.of(true, withChild);
105 } else {
106 result = IControlSelectionFilter.NON_MATCH;
107 }
108 return result;
109 })
110 .orElse(NON_MATCH);
111 }
112
113 @SuppressWarnings("PMD.ImplicitSwitchFallThrough")
114 private static Pattern toPattern(@NonNull Matching matching) {
115 String pattern = ObjectUtils.requireNonNull(matching.getPattern());
116 String regex = pattern.chars().boxed().map(ch -> (char) ch.intValue()).map(ch -> {
117
118 String value;
119 switch (ch) {
120 case '*':
121 value = ".*";
122 break;
123 case '?':
124 value = ".";
125 break;
126 case '.':
127 case '+':
128 case '\\':
129 case '[':
130 case ']':
131 case '{':
132 case '}':
133 case '(':
134 case ')':
135 case '^':
136 case '$':
137 value = "\\" + ch;
138 break;
139 default:
140 value = String.valueOf(ch);
141 }
142 return value;
143 }).collect(Collectors.joining("", "^", "$"));
144
145 if (LOGGER.isTraceEnabled()) {
146 LOGGER.atTrace().log("regex: {}", regex);
147 }
148 return Pattern.compile(regex);
149 }
150
151 private static class Selection {
152
153 private final boolean withChildControls;
154 private final Set<String> identifiers;
155 private final List<Pattern> patterns;
156
157 public Selection(IProfileSelectControlById selection) {
158
159
160 this.withChildControls = "yes".equals(selection.getWithChildControls());
161
162
163 List<String> ids = selection.getWithIds();
164 if (ids == null) {
165 ids = Collections.emptyList();
166 }
167 this.identifiers = ids.stream()
168 .filter(Objects::nonNull)
169 .collect(Collectors.toUnmodifiableSet());
170
171
172 List<Matching> matching = selection.getMatching();
173 if (matching == null) {
174 matching = Collections.emptyList();
175 }
176 this.patterns = matching.stream()
177 .filter(Objects::nonNull)
178 .map(DefaultControlSelectionFilter::toPattern)
179 .collect(Collectors.toUnmodifiableList());
180 }
181
182 public boolean isWithChildControls() {
183 return withChildControls;
184 }
185
186 @NonNull
187 protected Pair<Boolean, Boolean> match(String id) {
188
189 boolean result = identifiers.stream().anyMatch(controlIdentifier -> controlIdentifier.equals(id));
190 if (!result) {
191
192 result = patterns.stream().anyMatch(pattern -> pattern.asMatchPredicate().test(id));
193 }
194 return ObjectUtils.notNull(Pair.of(result, isWithChildControls()));
195 }
196 }
197 }