1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.syncope.core.logic;
20
21 import java.lang.reflect.Method;
22 import java.util.ArrayList;
23 import java.util.List;
24 import java.util.Optional;
25 import java.util.Set;
26 import java.util.stream.Collectors;
27 import java.util.stream.Stream;
28 import org.apache.commons.lang3.ArrayUtils;
29 import org.apache.commons.lang3.StringUtils;
30 import org.apache.commons.lang3.tuple.Pair;
31 import org.apache.commons.lang3.tuple.Triple;
32 import org.apache.syncope.common.lib.SyncopeClientException;
33 import org.apache.syncope.common.lib.SyncopeConstants;
34 import org.apache.syncope.common.lib.to.ConnObject;
35 import org.apache.syncope.common.lib.to.Item;
36 import org.apache.syncope.common.lib.to.Provision;
37 import org.apache.syncope.common.lib.to.ResourceTO;
38 import org.apache.syncope.common.lib.types.ClientExceptionType;
39 import org.apache.syncope.common.lib.types.IdMEntitlement;
40 import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
41 import org.apache.syncope.core.persistence.api.dao.ConnInstanceDAO;
42 import org.apache.syncope.core.persistence.api.dao.DuplicateException;
43 import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
44 import org.apache.syncope.core.persistence.api.dao.NotFoundException;
45 import org.apache.syncope.core.persistence.api.dao.VirSchemaDAO;
46 import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
47 import org.apache.syncope.core.persistence.api.entity.Any;
48 import org.apache.syncope.core.persistence.api.entity.AnyType;
49 import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
50 import org.apache.syncope.core.persistence.api.entity.ConnInstance;
51 import org.apache.syncope.core.persistence.api.entity.ExternalResource;
52 import org.apache.syncope.core.persistence.api.entity.VirSchema;
53 import org.apache.syncope.core.provisioning.api.Connector;
54 import org.apache.syncope.core.provisioning.api.ConnectorManager;
55 import org.apache.syncope.core.provisioning.api.MappingManager;
56 import org.apache.syncope.core.provisioning.api.VirAttrHandler;
57 import org.apache.syncope.core.provisioning.api.data.ConnInstanceDataBinder;
58 import org.apache.syncope.core.provisioning.api.data.ResourceDataBinder;
59 import org.apache.syncope.core.provisioning.api.utils.RealmUtils;
60 import org.apache.syncope.core.provisioning.java.pushpull.OutboundMatcher;
61 import org.apache.syncope.core.provisioning.java.utils.ConnObjectUtils;
62 import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
63 import org.apache.syncope.core.spring.security.AuthContextUtils;
64 import org.apache.syncope.core.spring.security.DelegatedAdministrationException;
65 import org.identityconnectors.framework.common.objects.ConnectorObject;
66 import org.identityconnectors.framework.common.objects.ObjectClass;
67 import org.identityconnectors.framework.common.objects.OperationOptions;
68 import org.identityconnectors.framework.common.objects.SearchResult;
69 import org.identityconnectors.framework.common.objects.filter.Filter;
70 import org.identityconnectors.framework.spi.SearchResultsHandler;
71 import org.springframework.security.access.prepost.PreAuthorize;
72 import org.springframework.transaction.annotation.Transactional;
73
74 public class ResourceLogic extends AbstractTransactionalLogic<ResourceTO> {
75
76 protected final ExternalResourceDAO resourceDAO;
77
78 protected final AnyTypeDAO anyTypeDAO;
79
80 protected final ConnInstanceDAO connInstanceDAO;
81
82 protected final VirSchemaDAO virSchemaDAO;
83
84 protected final VirAttrHandler virAttrHandler;
85
86 protected final ResourceDataBinder binder;
87
88 protected final ConnInstanceDataBinder connInstanceDataBinder;
89
90 protected final OutboundMatcher outboundMatcher;
91
92 protected final MappingManager mappingManager;
93
94 protected final ConnectorManager connectorManager;
95
96 protected final AnyUtilsFactory anyUtilsFactory;
97
98 public ResourceLogic(
99 final ExternalResourceDAO resourceDAO,
100 final AnyTypeDAO anyTypeDAO,
101 final ConnInstanceDAO connInstanceDAO,
102 final VirSchemaDAO virSchemaDAO,
103 final VirAttrHandler virAttrHandler,
104 final ResourceDataBinder binder,
105 final ConnInstanceDataBinder connInstanceDataBinder,
106 final OutboundMatcher outboundMatcher,
107 final MappingManager mappingManager,
108 final ConnectorManager connectorManager,
109 final AnyUtilsFactory anyUtilsFactory) {
110
111 this.resourceDAO = resourceDAO;
112 this.anyTypeDAO = anyTypeDAO;
113 this.connInstanceDAO = connInstanceDAO;
114 this.virSchemaDAO = virSchemaDAO;
115 this.virAttrHandler = virAttrHandler;
116 this.binder = binder;
117 this.connInstanceDataBinder = connInstanceDataBinder;
118 this.outboundMatcher = outboundMatcher;
119 this.mappingManager = mappingManager;
120 this.connectorManager = connectorManager;
121 this.anyUtilsFactory = anyUtilsFactory;
122 }
123
124 protected void securityChecks(final Set<String> effectiveRealms, final String realm, final String key) {
125 boolean authorized = effectiveRealms.stream().anyMatch(realm::startsWith);
126 if (!authorized) {
127 throw new DelegatedAdministrationException(realm, ExternalResource.class.getSimpleName(), key);
128 }
129 }
130
131 protected ExternalResource doSave(final ExternalResource resource) {
132 ExternalResource merged = resourceDAO.save(resource);
133 try {
134 connectorManager.registerConnector(merged);
135 } catch (NotFoundException e) {
136 LOG.error("While registering connector for resource", e);
137 }
138 return merged;
139 }
140
141 @PreAuthorize("hasRole('" + IdMEntitlement.RESOURCE_CREATE + "')")
142 public ResourceTO create(final ResourceTO resourceTO) {
143 if (StringUtils.isBlank(resourceTO.getKey())) {
144 SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.RequiredValuesMissing);
145 sce.getElements().add("Resource key");
146 throw sce;
147 }
148
149 ConnInstance connInstance = connInstanceDAO.authFind(resourceTO.getConnector());
150 if (connInstance == null) {
151 SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidExternalResource);
152 sce.getElements().add("Connector " + resourceTO.getConnector());
153 throw sce;
154 }
155
156 Set<String> effectiveRealms = RealmUtils.getEffective(
157 AuthContextUtils.getAuthorizations().get(IdMEntitlement.RESOURCE_CREATE),
158 connInstance.getAdminRealm().getFullPath());
159 securityChecks(effectiveRealms, connInstance.getAdminRealm().getFullPath(), null);
160
161 if (resourceDAO.authFind(resourceTO.getKey()) != null) {
162 throw new DuplicateException(resourceTO.getKey());
163 }
164
165 return binder.getResourceTO(doSave(binder.create(resourceTO)));
166 }
167
168 @PreAuthorize("hasRole('" + IdMEntitlement.RESOURCE_UPDATE + "')")
169 public ResourceTO update(final ResourceTO resourceTO) {
170 ExternalResource resource = Optional.ofNullable(resourceDAO.authFind(resourceTO.getKey())).
171 orElseThrow(() -> new NotFoundException("Resource '" + resourceTO.getKey() + '\''));
172
173 Set<String> effectiveRealms = RealmUtils.getEffective(
174 AuthContextUtils.getAuthorizations().get(IdMEntitlement.RESOURCE_UPDATE),
175 resource.getConnector().getAdminRealm().getFullPath());
176 securityChecks(effectiveRealms, resource.getConnector().getAdminRealm().getFullPath(), resource.getKey());
177
178 return binder.getResourceTO(doSave(binder.update(resource, resourceTO)));
179 }
180
181 @PreAuthorize("hasRole('" + IdMEntitlement.RESOURCE_UPDATE + "')")
182 public void setLatestSyncToken(final String key, final String anyTypeKey) {
183 ExternalResource resource = Optional.ofNullable(resourceDAO.authFind(key)).
184 orElseThrow(() -> new NotFoundException("Resource '" + key + '\''));
185
186 Connector connector;
187 try {
188 connector = connectorManager.getConnector(resource);
189 } catch (Exception e) {
190 SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidConnInstance);
191 sce.getElements().add(e.getMessage());
192 throw sce;
193 }
194
195 if (SyncopeConstants.REALM_ANYTYPE.equals(anyTypeKey)) {
196 if (resource.getOrgUnit() == null) {
197 throw new NotFoundException("Realm provision not enabled for Resource '" + key + '\'');
198 }
199
200 resource.getOrgUnit().setSyncToken(ConnObjectUtils.toString(
201 connector.getLatestSyncToken(new ObjectClass(resource.getOrgUnit().getObjectClass()))));
202 } else {
203 AnyType anyType = Optional.ofNullable(anyTypeDAO.find(anyTypeKey)).
204 orElseThrow(() -> new NotFoundException("AnyType '" + anyTypeKey + '\''));
205 Provision provision = resource.getProvisionByAnyType(anyType.getKey()).
206 orElseThrow(() -> new NotFoundException(
207 "Provision for AnyType '" + anyTypeKey + "' in Resource '" + key + '\''));
208
209 provision.setSyncToken(ConnObjectUtils.toString(
210 connector.getLatestSyncToken(new ObjectClass(provision.getObjectClass()))));
211 }
212
213 Set<String> effectiveRealms = RealmUtils.getEffective(
214 AuthContextUtils.getAuthorizations().get(IdMEntitlement.RESOURCE_UPDATE),
215 resource.getConnector().getAdminRealm().getFullPath());
216 securityChecks(effectiveRealms, resource.getConnector().getAdminRealm().getFullPath(), resource.getKey());
217
218 doSave(resource);
219 }
220
221 @PreAuthorize("hasRole('" + IdMEntitlement.RESOURCE_UPDATE + "')")
222 public void removeSyncToken(final String key, final String anyTypeKey) {
223 ExternalResource resource = Optional.ofNullable(resourceDAO.authFind(key)).
224 orElseThrow(() -> new NotFoundException("Resource '" + key + '\''));
225 if (SyncopeConstants.REALM_ANYTYPE.equals(anyTypeKey)) {
226 if (resource.getOrgUnit() == null) {
227 throw new NotFoundException("Realm provision not enabled for Resource '" + key + '\'');
228 }
229
230 resource.getOrgUnit().setSyncToken(null);
231 } else {
232 AnyType anyType = Optional.ofNullable(anyTypeDAO.find(anyTypeKey)).
233 orElseThrow(() -> new NotFoundException("AnyType '" + anyTypeKey + '\''));
234 Provision provision = resource.getProvisionByAnyType(anyType.getKey()).
235 orElseThrow(() -> new NotFoundException(
236 "Provision for AnyType '" + anyTypeKey + "' in Resource '" + key + '\''));
237
238 provision.setSyncToken(null);
239 }
240
241 Set<String> effectiveRealms = RealmUtils.getEffective(
242 AuthContextUtils.getAuthorizations().get(IdMEntitlement.RESOURCE_UPDATE),
243 resource.getConnector().getAdminRealm().getFullPath());
244 securityChecks(effectiveRealms, resource.getConnector().getAdminRealm().getFullPath(), resource.getKey());
245
246 doSave(resource);
247 }
248
249 @PreAuthorize("hasRole('" + IdMEntitlement.RESOURCE_DELETE + "')")
250 public ResourceTO delete(final String key) {
251 ExternalResource resource = Optional.ofNullable(resourceDAO.authFind(key)).
252 orElseThrow(() -> new NotFoundException("Resource '" + key + '\''));
253
254 Set<String> effectiveRealms = RealmUtils.getEffective(
255 AuthContextUtils.getAuthorizations().get(IdMEntitlement.RESOURCE_DELETE),
256 resource.getConnector().getAdminRealm().getFullPath());
257 securityChecks(effectiveRealms, resource.getConnector().getAdminRealm().getFullPath(), resource.getKey());
258
259 connectorManager.unregisterConnector(resource);
260
261 ResourceTO deleted = binder.getResourceTO(resource);
262 resourceDAO.delete(key);
263 return deleted;
264 }
265
266 @PreAuthorize("hasRole('" + IdMEntitlement.RESOURCE_READ + "')")
267 @Transactional(readOnly = true)
268 public ResourceTO read(final String key) {
269 ExternalResource resource = Optional.ofNullable(resourceDAO.authFind(key)).
270 orElseThrow(() -> new NotFoundException("Resource '" + key + '\''));
271
272 return binder.getResourceTO(resource);
273 }
274
275 @PreAuthorize("hasRole('" + IdMEntitlement.RESOURCE_LIST + "')")
276 @Transactional(readOnly = true)
277 public List<ResourceTO> list() {
278 return resourceDAO.findAll().stream().map(binder::getResourceTO).collect(Collectors.toList());
279 }
280
281 protected Triple<AnyType, ExternalResource, Provision> getProvision(
282 final String anyTypeKey, final String resourceKey) {
283
284 AnyType anyType = Optional.ofNullable(anyTypeDAO.find(anyTypeKey)).
285 orElseThrow(() -> new NotFoundException("AnyType '" + anyTypeKey + '\''));
286
287 ExternalResource resource = Optional.ofNullable(resourceDAO.authFind(resourceKey)).
288 orElseThrow(() -> new NotFoundException("Resource '" + resourceKey + '\''));
289 Provision provision = resource.getProvisionByAnyType(anyType.getKey()).
290 orElseThrow(() -> new NotFoundException(
291 "Provision for " + anyType + " on Resource '" + resourceKey + "'"));
292 if (provision.getMapping() == null) {
293 throw new NotFoundException("Mapping for " + anyType + " on Resource '" + resourceKey + "'");
294 }
295
296 return Triple.of(anyType, resource, provision);
297 }
298
299 @PreAuthorize("hasRole('" + IdMEntitlement.RESOURCE_GET_CONNOBJECT + "')")
300 @Transactional(readOnly = true)
301 public String getConnObjectKeyValue(
302 final String key,
303 final String anyTypeKey,
304 final String anyKey) {
305
306 Triple<AnyType, ExternalResource, Provision> triple = getProvision(anyTypeKey, key);
307
308
309 Any<?> any = Optional.ofNullable(anyUtilsFactory.getInstance(triple.getLeft().getKind()).
310 dao().authFind(anyKey)).
311 orElseThrow(() -> new NotFoundException(triple.getLeft() + " " + anyKey));
312
313
314 return mappingManager.getConnObjectKeyValue(any, triple.getMiddle(), triple.getRight()).
315 orElseThrow(() -> new NotFoundException(
316 "No ConnObjectKey value found for " + anyTypeKey + " " + anyKey + " on resource '" + key + "'"));
317 }
318
319 @PreAuthorize("hasRole('" + IdMEntitlement.RESOURCE_GET_CONNOBJECT + "')")
320 @Transactional(readOnly = true)
321 public ConnObject readConnObjectByAnyKey(
322 final String key,
323 final String anyTypeKey,
324 final String anyKey) {
325
326 Triple<AnyType, ExternalResource, Provision> triple = getProvision(anyTypeKey, key);
327
328
329 Any<?> any = Optional.ofNullable(anyUtilsFactory.getInstance(triple.getLeft().getKind()).
330 dao().authFind(anyKey)).
331 orElseThrow(() -> new NotFoundException(triple.getLeft() + " " + anyKey));
332
333
334 List<ConnectorObject> connObjs = outboundMatcher.match(
335 connectorManager.getConnector(triple.getMiddle()),
336 any,
337 triple.getMiddle(),
338 triple.getRight(),
339 Optional.empty());
340 if (connObjs.isEmpty()) {
341 throw new NotFoundException(
342 "Object " + any + " with class " + triple.getRight().getObjectClass()
343 + " not found on resource " + triple.getMiddle().getKey());
344 }
345
346 if (connObjs.size() > 1) {
347 LOG.warn("Expected single match, found {}", connObjs);
348 } else {
349 virAttrHandler.setValues(any, connObjs.get(0));
350 }
351
352 return ConnObjectUtils.getConnObjectTO(
353 outboundMatcher.getFIQL(connObjs.get(0), triple.getMiddle(), triple.getRight()),
354 connObjs.get(0).getAttributes());
355 }
356
357 @PreAuthorize("hasRole('" + IdMEntitlement.RESOURCE_GET_CONNOBJECT + "')")
358 @Transactional(readOnly = true)
359 public ConnObject readConnObjectByConnObjectKeyValue(
360 final String key,
361 final String anyTypeKey,
362 final String connObjectKeyValue) {
363
364 Triple<AnyType, ExternalResource, Provision> triple = getProvision(anyTypeKey, key);
365
366 Item connObjectKeyItem = MappingUtils.getConnObjectKeyItem(triple.getRight()).
367 orElseThrow(() -> new NotFoundException(
368 "ConnObjectKey mapping for " + triple.getLeft().getKey()
369 + " on resource '" + triple.getMiddle().getKey() + "'"));
370
371 return outboundMatcher.matchByConnObjectKeyValue(
372 connectorManager.getConnector(triple.getMiddle()),
373 connObjectKeyItem,
374 connObjectKeyValue,
375 triple.getMiddle(),
376 triple.getRight(),
377 Optional.empty(),
378 Optional.empty()).
379 map(connectorObject -> ConnObjectUtils.getConnObjectTO(
380 outboundMatcher.getFIQL(connectorObject, triple.getMiddle(), triple.getRight()),
381 connectorObject.getAttributes())).
382 orElseThrow(() -> new NotFoundException(
383 "Object " + connObjectKeyValue + " with class " + triple.getRight().getObjectClass()
384 + " not found on resource " + triple.getMiddle().getKey()));
385 }
386
387 @PreAuthorize("hasRole('" + IdMEntitlement.RESOURCE_LIST_CONNOBJECT + "')")
388 @Transactional(readOnly = true)
389 public Pair<SearchResult, List<ConnObject>> searchConnObjects(
390 final Filter filter,
391 final Set<String> moreAttrsToGet,
392 final String key,
393 final String anyTypeKey,
394 final int size,
395 final String pagedResultsCookie,
396 final List<OrderByClause> orderBy) {
397
398 ExternalResource resource;
399 Provision provision;
400 ObjectClass objectClass;
401 OperationOptions options;
402 if (SyncopeConstants.REALM_ANYTYPE.equals(anyTypeKey)) {
403 resource = resourceDAO.find(key);
404 if (resource == null) {
405 throw new NotFoundException("Resource '" + key + '\'');
406 }
407 if (resource.getOrgUnit() == null) {
408 throw new NotFoundException("Realm provisioning for resource '" + key + '\'');
409 }
410
411 provision = null;
412 objectClass = new ObjectClass(resource.getOrgUnit().getObjectClass());
413 options = MappingUtils.buildOperationOptions(
414 resource.getOrgUnit().getItems().stream(), moreAttrsToGet.toArray(String[]::new));
415 } else {
416 Triple<AnyType, ExternalResource, Provision> triple = getProvision(anyTypeKey, key);
417
418 provision = triple.getRight();
419 resource = triple.getMiddle();
420 objectClass = new ObjectClass(provision.getObjectClass());
421
422 Stream<Item> mapItems = Stream.concat(
423 provision.getMapping().getItems().stream(),
424 virSchemaDAO.find(resource.getKey(), triple.getLeft().getKey()).
425 stream().map(VirSchema::asLinkingMappingItem));
426 options = MappingUtils.buildOperationOptions(mapItems, moreAttrsToGet.toArray(String[]::new));
427 }
428
429 List<ConnObject> connObjects = new ArrayList<>();
430 SearchResult searchResult = connectorManager.getConnector(resource).
431 search(objectClass, filter, new SearchResultsHandler() {
432
433 private int count;
434
435 @Override
436 public boolean handle(final ConnectorObject connectorObject) {
437 connObjects.add(ConnObjectUtils.getConnObjectTO(
438 provision == null
439 ? null : outboundMatcher.getFIQL(connectorObject, resource, provision),
440 connectorObject.getAttributes()));
441
442 count++;
443 return count < size;
444 }
445
446 @Override
447 public void handleResult(final SearchResult sr) {
448
449 }
450 }, size, pagedResultsCookie, orderBy, options);
451
452 return Pair.of(searchResult, connObjects);
453 }
454
455 @PreAuthorize("hasRole('" + IdMEntitlement.CONNECTOR_READ + "')")
456 @Transactional(readOnly = true)
457 public void check(final ResourceTO resourceTO) {
458 ConnInstance connInstance = Optional.ofNullable(connInstanceDAO.find(resourceTO.getConnector())).
459 orElseThrow(() -> new NotFoundException("Connector '" + resourceTO.getConnector() + '\''));
460
461 connectorManager.createConnector(
462 connectorManager.buildConnInstanceOverride(
463 connInstanceDataBinder.getConnInstanceTO(connInstance),
464 resourceTO.getConfOverride(),
465 resourceTO.isOverrideCapabilities()
466 ? Optional.of(resourceTO.getCapabilitiesOverride()) : Optional.empty())).
467 test();
468 }
469
470 @Override
471 protected ResourceTO resolveReference(final Method method, final Object... args)
472 throws UnresolvedReferenceException {
473
474 String key = null;
475
476 if (ArrayUtils.isNotEmpty(args)) {
477 for (int i = 0; key == null && i < args.length; i++) {
478 if (args[i] instanceof String) {
479 key = (String) args[i];
480 } else if (args[i] instanceof ResourceTO) {
481 key = ((ResourceTO) args[i]).getKey();
482 }
483 }
484 }
485
486 if (key != null) {
487 try {
488 return binder.getResourceTO(resourceDAO.find(key));
489 } catch (Throwable ignore) {
490 LOG.debug("Unresolved reference", ignore);
491 throw new UnresolvedReferenceException(ignore);
492 }
493 }
494
495 throw new UnresolvedReferenceException();
496 }
497 }