1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.syncope.core.provisioning.java.pushpull;
20
21 import java.util.ArrayList;
22 import java.util.Collection;
23 import java.util.HashSet;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Optional;
27 import java.util.Set;
28 import java.util.concurrent.ConcurrentHashMap;
29 import java.util.stream.Stream;
30 import org.apache.commons.lang3.ArrayUtils;
31 import org.apache.syncope.common.lib.to.Item;
32 import org.apache.syncope.common.lib.to.Provision;
33 import org.apache.syncope.common.lib.types.AnyTypeKind;
34 import org.apache.syncope.core.persistence.api.dao.UserDAO;
35 import org.apache.syncope.core.persistence.api.dao.VirSchemaDAO;
36 import org.apache.syncope.core.persistence.api.entity.Any;
37 import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
38 import org.apache.syncope.core.persistence.api.entity.ExternalResource;
39 import org.apache.syncope.core.persistence.api.entity.Implementation;
40 import org.apache.syncope.core.persistence.api.entity.VirSchema;
41 import org.apache.syncope.core.persistence.api.entity.policy.PushCorrelationRuleEntity;
42 import org.apache.syncope.core.provisioning.api.Connector;
43 import org.apache.syncope.core.provisioning.api.MappingManager;
44 import org.apache.syncope.core.provisioning.api.TimeoutException;
45 import org.apache.syncope.core.provisioning.api.VirAttrHandler;
46 import org.apache.syncope.core.provisioning.api.propagation.PropagationActions;
47 import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskInfo;
48 import org.apache.syncope.core.provisioning.api.rules.PushCorrelationRule;
49 import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
50 import org.apache.syncope.core.spring.implementation.ImplementationManager;
51 import org.identityconnectors.framework.common.objects.AttributeBuilder;
52 import org.identityconnectors.framework.common.objects.ConnectorObject;
53 import org.identityconnectors.framework.common.objects.ObjectClass;
54 import org.identityconnectors.framework.common.objects.SearchResult;
55 import org.identityconnectors.framework.common.objects.filter.Filter;
56 import org.identityconnectors.framework.spi.SearchResultsHandler;
57 import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory;
59 import org.springframework.transaction.annotation.Transactional;
60
61 public class OutboundMatcher {
62
63 protected static final Logger LOG = LoggerFactory.getLogger(OutboundMatcher.class);
64
65 protected final MappingManager mappingManager;
66
67 protected final UserDAO userDAO;
68
69 protected final AnyUtilsFactory anyUtilsFactory;
70
71 protected final VirSchemaDAO virSchemaDAO;
72
73 protected final VirAttrHandler virAttrHandler;
74
75 protected final Map<String, PropagationActions> perContextActions = new ConcurrentHashMap<>();
76
77 protected final Map<String, PushCorrelationRule> perContextPushCorrelationRules = new ConcurrentHashMap<>();
78
79 public OutboundMatcher(
80 final MappingManager mappingManager,
81 final UserDAO userDAO,
82 final AnyUtilsFactory anyUtilsFactory,
83 final VirSchemaDAO virSchemaDAO,
84 final VirAttrHandler virAttrHandler) {
85
86 this.mappingManager = mappingManager;
87 this.userDAO = userDAO;
88 this.anyUtilsFactory = anyUtilsFactory;
89 this.virSchemaDAO = virSchemaDAO;
90 this.virAttrHandler = virAttrHandler;
91 }
92
93 protected Optional<PushCorrelationRule> rule(final ExternalResource resource, final Provision provision) {
94 Optional<? extends PushCorrelationRuleEntity> correlationRule = resource.getPushPolicy() == null
95 ? Optional.empty()
96 : resource.getPushPolicy().getCorrelationRule(provision.getAnyType());
97
98 Optional<PushCorrelationRule> rule = Optional.empty();
99 if (correlationRule.isPresent()) {
100 Implementation impl = correlationRule.get().getImplementation();
101 try {
102 rule = ImplementationManager.buildPushCorrelationRule(
103 impl,
104 () -> perContextPushCorrelationRules.get(impl.getKey()),
105 instance -> perContextPushCorrelationRules.put(impl.getKey(), instance));
106 } catch (Exception e) {
107 LOG.error("While building {}", impl, e);
108 }
109 }
110
111 return rule;
112 }
113
114 public String getFIQL(
115 final ConnectorObject connectorObject,
116 final ExternalResource resource,
117 final Provision provision) {
118
119 return rule(resource, provision).
120 map(rule -> rule.getFIQL(connectorObject, provision)).
121 orElseGet(() -> PushCorrelationRule.DEFAULT_FIQL_BUILDER.apply(connectorObject, provision));
122 }
123
124 public List<ConnectorObject> match(
125 final PropagationTaskInfo taskInfo,
126 final Connector connector,
127 final Provision provision,
128 final List<PropagationActions> actions,
129 final String connObjectKeyValue) {
130
131 Optional<PushCorrelationRule> rule = rule(taskInfo.getResource(), provision);
132
133 boolean isLinkedAccount = taskInfo.getAnyTypeKind() == AnyTypeKind.USER
134 && userDAO.linkedAccountExists(taskInfo.getEntityKey(), connObjectKeyValue);
135 Any<?> any = null;
136 if (!isLinkedAccount) {
137 any = anyUtilsFactory.getInstance(taskInfo.getAnyTypeKind()).dao().find(taskInfo.getEntityKey());
138 }
139
140 Set<String> moreAttrsToGet = new HashSet<>();
141 actions.forEach(action -> moreAttrsToGet.addAll(action.moreAttrsToGet(Optional.of(taskInfo), provision)));
142
143 List<ConnectorObject> result = new ArrayList<>();
144 try {
145 if (any != null && rule.isPresent()) {
146 result.addAll(matchByCorrelationRule(
147 connector,
148 rule.get().getFilter(any, taskInfo.getResource(), provision),
149 taskInfo.getResource(),
150 provision,
151 Optional.of(moreAttrsToGet.toArray(String[]::new)),
152 Optional.empty()));
153 } else {
154 MappingUtils.getConnObjectKeyItem(provision).flatMap(connObjectKeyItem -> matchByConnObjectKeyValue(
155 connector,
156 connObjectKeyItem,
157 connObjectKeyValue,
158 taskInfo.getResource(),
159 provision,
160 Optional.of(moreAttrsToGet.toArray(String[]::new)),
161 Optional.empty())).ifPresent(result::add);
162 }
163 } catch (RuntimeException e) {
164 LOG.error("Could not match {} with any existing {}", any, provision.getObjectClass(), e);
165 }
166
167 if (any != null && result.size() == 1) {
168 virAttrHandler.setValues(any, result.get(0));
169 }
170
171 return result;
172 }
173
174 protected List<PropagationActions> getPropagationActions(final ExternalResource resource) {
175 List<PropagationActions> result = new ArrayList<>();
176
177 resource.getPropagationActions().forEach(impl -> {
178 try {
179 result.add(ImplementationManager.build(
180 impl,
181 () -> perContextActions.get(impl.getKey()),
182 instance -> perContextActions.put(impl.getKey(), instance)));
183 } catch (Exception e) {
184 LOG.error("While building {}", impl, e);
185 }
186 });
187
188 return result;
189 }
190
191 @Transactional(readOnly = true)
192 public List<ConnectorObject> match(
193 final Connector connector,
194 final Any<?> any,
195 final ExternalResource resource,
196 final Provision provision,
197 final Optional<String[]> moreAttrsToGet,
198 final Item... linkingItems) {
199
200 Stream<String> matgFromPropagationActions = getPropagationActions(resource).stream().
201 flatMap(a -> a.moreAttrsToGet(Optional.empty(), provision).stream());
202 Optional<String[]> effectiveMATG = Optional.of(Stream.concat(
203 moreAttrsToGet.stream().flatMap(Stream::of),
204 matgFromPropagationActions).toArray(String[]::new));
205
206 Optional<PushCorrelationRule> rule = rule(resource, provision);
207
208 List<ConnectorObject> result = new ArrayList<>();
209 try {
210 if (rule.isPresent()) {
211 result.addAll(matchByCorrelationRule(
212 connector,
213 rule.get().getFilter(any, resource, provision),
214 resource,
215 provision,
216 effectiveMATG,
217 ArrayUtils.isEmpty(linkingItems)
218 ? Optional.empty() : Optional.of(List.of(linkingItems))));
219 } else {
220 Optional<Item> connObjectKeyItem = MappingUtils.getConnObjectKeyItem(provision);
221 Optional<String> connObjectKeyValue = mappingManager.getConnObjectKeyValue(any, resource, provision);
222
223 if (connObjectKeyItem.isPresent() && connObjectKeyValue.isPresent()) {
224 matchByConnObjectKeyValue(
225 connector,
226 connObjectKeyItem.get(),
227 connObjectKeyValue.get(),
228 resource,
229 provision,
230 effectiveMATG,
231 ArrayUtils.isEmpty(linkingItems)
232 ? Optional.empty() : Optional.of(List.of(linkingItems))).
233 ifPresent(result::add);
234 }
235 }
236 } catch (RuntimeException e) {
237 LOG.error("Could not match {} with any existing {}", any, provision.getObjectClass(), e);
238 }
239
240 if (any != null && result.size() == 1) {
241 virAttrHandler.setValues(any, result.get(0));
242 }
243
244 return result;
245 }
246
247 protected List<ConnectorObject> matchByCorrelationRule(
248 final Connector connector,
249 final Filter filter,
250 final ExternalResource resource,
251 final Provision provision,
252 final Optional<String[]> moreAttrsToGet,
253 final Optional<Collection<Item>> linkingItems) {
254
255 Stream<Item> items = Stream.concat(
256 provision.getMapping().getItems().stream(),
257 linkingItems.isPresent()
258 ? linkingItems.get().stream()
259 : virSchemaDAO.find(resource.getKey(), provision.getAnyType()).stream().
260 map(VirSchema::asLinkingMappingItem));
261
262 List<ConnectorObject> objs = new ArrayList<>();
263 try {
264 connector.search(new ObjectClass(provision.getObjectClass()), filter, new SearchResultsHandler() {
265
266 @Override
267 public void handleResult(final SearchResult result) {
268
269 }
270
271 @Override
272 public boolean handle(final ConnectorObject connectorObject) {
273 objs.add(connectorObject);
274 return true;
275 }
276 }, MappingUtils.buildOperationOptions(items, moreAttrsToGet.orElse(null)));
277 } catch (TimeoutException toe) {
278 LOG.debug("Request timeout", toe);
279 throw toe;
280 } catch (RuntimeException ignore) {
281 LOG.debug("Unexpected exception", ignore);
282 }
283
284 return objs;
285 }
286
287 @Transactional(readOnly = true)
288 public Optional<ConnectorObject> matchByConnObjectKeyValue(
289 final Connector connector,
290 final Item connObjectKeyItem,
291 final String connObjectKeyValue,
292 final ExternalResource resource,
293 final Provision provision,
294 final Optional<String[]> moreAttrsToGet,
295 final Optional<Collection<Item>> linkingItems) {
296
297 Stream<Item> items = Stream.concat(
298 provision.getMapping().getItems().stream(),
299 linkingItems.isPresent()
300 ? linkingItems.get().stream()
301 : virSchemaDAO.find(resource.getKey(), provision.getAnyType()).stream().
302 map(VirSchema::asLinkingMappingItem));
303
304 ConnectorObject obj = null;
305 try {
306 obj = connector.getObject(
307 new ObjectClass(provision.getObjectClass()),
308 AttributeBuilder.build(connObjectKeyItem.getExtAttrName(), connObjectKeyValue),
309 provision.isIgnoreCaseMatch(),
310 MappingUtils.buildOperationOptions(items, moreAttrsToGet.orElse(null)));
311 } catch (TimeoutException toe) {
312 LOG.debug("Request timeout", toe);
313 throw toe;
314 } catch (RuntimeException ignore) {
315 LOG.debug("While resolving {}", connObjectKeyValue, ignore);
316 }
317
318 return Optional.ofNullable(obj);
319 }
320 }