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;
20
21 import java.text.ParseException;
22 import java.time.temporal.TemporalAccessor;
23 import java.util.ArrayList;
24 import java.util.Base64;
25 import java.util.HashSet;
26 import java.util.List;
27 import java.util.Objects;
28 import java.util.Optional;
29 import java.util.Set;
30 import java.util.stream.Collectors;
31 import org.apache.commons.jexl3.JexlContext;
32 import org.apache.commons.jexl3.MapContext;
33 import org.apache.commons.lang3.BooleanUtils;
34 import org.apache.commons.lang3.StringUtils;
35 import org.apache.commons.lang3.reflect.FieldUtils;
36 import org.apache.commons.lang3.tuple.Pair;
37 import org.apache.syncope.common.lib.Attr;
38 import org.apache.syncope.common.lib.to.AnyObjectTO;
39 import org.apache.syncope.common.lib.to.AnyTO;
40 import org.apache.syncope.common.lib.to.GroupTO;
41 import org.apache.syncope.common.lib.to.GroupableRelatableTO;
42 import org.apache.syncope.common.lib.to.Item;
43 import org.apache.syncope.common.lib.to.Mapping;
44 import org.apache.syncope.common.lib.to.MembershipTO;
45 import org.apache.syncope.common.lib.to.OrgUnit;
46 import org.apache.syncope.common.lib.to.Provision;
47 import org.apache.syncope.common.lib.to.RealmTO;
48 import org.apache.syncope.common.lib.to.UserTO;
49 import org.apache.syncope.common.lib.types.AnyTypeKind;
50 import org.apache.syncope.common.lib.types.AttrSchemaType;
51 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
52 import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
53 import org.apache.syncope.core.persistence.api.dao.ApplicationDAO;
54 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
55 import org.apache.syncope.core.persistence.api.dao.ImplementationDAO;
56 import org.apache.syncope.core.persistence.api.dao.RealmDAO;
57 import org.apache.syncope.core.persistence.api.dao.RelationshipTypeDAO;
58 import org.apache.syncope.core.persistence.api.dao.UserDAO;
59 import org.apache.syncope.core.persistence.api.entity.Any;
60 import org.apache.syncope.core.persistence.api.entity.AnyType;
61 import org.apache.syncope.core.persistence.api.entity.AnyUtils;
62 import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
63 import org.apache.syncope.core.persistence.api.entity.Application;
64 import org.apache.syncope.core.persistence.api.entity.Attributable;
65 import org.apache.syncope.core.persistence.api.entity.DerSchema;
66 import org.apache.syncope.core.persistence.api.entity.ExternalResource;
67 import org.apache.syncope.core.persistence.api.entity.GroupableRelatable;
68 import org.apache.syncope.core.persistence.api.entity.Implementation;
69 import org.apache.syncope.core.persistence.api.entity.Membership;
70 import org.apache.syncope.core.persistence.api.entity.PlainAttr;
71 import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
72 import org.apache.syncope.core.persistence.api.entity.PlainSchema;
73 import org.apache.syncope.core.persistence.api.entity.Realm;
74 import org.apache.syncope.core.persistence.api.entity.Relationship;
75 import org.apache.syncope.core.persistence.api.entity.RelationshipType;
76 import org.apache.syncope.core.persistence.api.entity.VirSchema;
77 import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
78 import org.apache.syncope.core.persistence.api.entity.group.Group;
79 import org.apache.syncope.core.persistence.api.entity.user.Account;
80 import org.apache.syncope.core.persistence.api.entity.user.LAPlainAttr;
81 import org.apache.syncope.core.persistence.api.entity.user.LinkedAccount;
82 import org.apache.syncope.core.persistence.api.entity.user.User;
83 import org.apache.syncope.core.provisioning.api.AccountGetter;
84 import org.apache.syncope.core.provisioning.api.DerAttrHandler;
85 import org.apache.syncope.core.provisioning.api.IntAttrName;
86 import org.apache.syncope.core.provisioning.api.IntAttrNameParser;
87 import org.apache.syncope.core.provisioning.api.MappingManager;
88 import org.apache.syncope.core.provisioning.api.PlainAttrGetter;
89 import org.apache.syncope.core.provisioning.api.VirAttrHandler;
90 import org.apache.syncope.core.provisioning.api.cache.VirAttrCache;
91 import org.apache.syncope.core.provisioning.api.cache.VirAttrCacheKey;
92 import org.apache.syncope.core.provisioning.api.data.ItemTransformer;
93 import org.apache.syncope.core.provisioning.api.jexl.JexlUtils;
94 import org.apache.syncope.core.provisioning.api.utils.FormatUtils;
95 import org.apache.syncope.core.provisioning.java.utils.ConnObjectUtils;
96 import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
97 import org.apache.syncope.core.spring.security.Encryptor;
98 import org.identityconnectors.framework.common.FrameworkUtil;
99 import org.identityconnectors.framework.common.objects.Attribute;
100 import org.identityconnectors.framework.common.objects.AttributeBuilder;
101 import org.identityconnectors.framework.common.objects.AttributeUtil;
102 import org.identityconnectors.framework.common.objects.Name;
103 import org.identityconnectors.framework.common.objects.OperationalAttributes;
104 import org.identityconnectors.framework.common.objects.Uid;
105 import org.slf4j.Logger;
106 import org.slf4j.LoggerFactory;
107 import org.springframework.transaction.annotation.Transactional;
108
109 public class DefaultMappingManager implements MappingManager {
110
111 protected static final Logger LOG = LoggerFactory.getLogger(MappingManager.class);
112
113 protected static final Encryptor ENCRYPTOR = Encryptor.getInstance();
114
115 protected final AnyTypeDAO anyTypeDAO;
116
117 protected final UserDAO userDAO;
118
119 protected final AnyObjectDAO anyObjectDAO;
120
121 protected final GroupDAO groupDAO;
122
123 protected final RelationshipTypeDAO relationshipTypeDAO;
124
125 protected final RealmDAO realmDAO;
126
127 protected final ApplicationDAO applicationDAO;
128
129 protected final ImplementationDAO implementationDAO;
130
131 protected final DerAttrHandler derAttrHandler;
132
133 protected final VirAttrHandler virAttrHandler;
134
135 protected final VirAttrCache virAttrCache;
136
137 protected final AnyUtilsFactory anyUtilsFactory;
138
139 protected final IntAttrNameParser intAttrNameParser;
140
141 public DefaultMappingManager(
142 final AnyTypeDAO anyTypeDAO,
143 final UserDAO userDAO,
144 final AnyObjectDAO anyObjectDAO,
145 final GroupDAO groupDAO,
146 final RelationshipTypeDAO relationshipTypeDAO,
147 final RealmDAO realmDAO,
148 final ApplicationDAO applicationDAO,
149 final ImplementationDAO implementationDAO,
150 final DerAttrHandler derAttrHandler,
151 final VirAttrHandler virAttrHandler,
152 final VirAttrCache virAttrCache,
153 final AnyUtilsFactory anyUtilsFactory,
154 final IntAttrNameParser intAttrNameParser) {
155
156 this.anyTypeDAO = anyTypeDAO;
157 this.userDAO = userDAO;
158 this.anyObjectDAO = anyObjectDAO;
159 this.groupDAO = groupDAO;
160 this.relationshipTypeDAO = relationshipTypeDAO;
161 this.realmDAO = realmDAO;
162 this.applicationDAO = applicationDAO;
163 this.implementationDAO = implementationDAO;
164 this.derAttrHandler = derAttrHandler;
165 this.virAttrHandler = virAttrHandler;
166 this.virAttrCache = virAttrCache;
167 this.anyUtilsFactory = anyUtilsFactory;
168 this.intAttrNameParser = intAttrNameParser;
169 }
170
171 protected List<Implementation> getTransformers(final Item item) {
172 return item.getTransformers().stream().
173 map(implementationDAO::find).
174 filter(Objects::nonNull).
175 collect(Collectors.toList());
176 }
177
178 protected String processPreparedAttr(final Pair<String, Attribute> preparedAttr, final Set<Attribute> attributes) {
179 String connObjectKey = null;
180
181 if (preparedAttr != null) {
182 if (preparedAttr.getLeft() != null) {
183 connObjectKey = preparedAttr.getLeft();
184 }
185
186 if (preparedAttr.getRight() != null) {
187 Attribute alreadyAdded = AttributeUtil.find(preparedAttr.getRight().getName(), attributes);
188
189 if (alreadyAdded == null) {
190 attributes.add(preparedAttr.getRight());
191 } else {
192 attributes.remove(alreadyAdded);
193
194 Set<Object> values = new HashSet<>();
195 if (alreadyAdded.getValue() != null && !alreadyAdded.getValue().isEmpty()) {
196 values.addAll(alreadyAdded.getValue());
197 }
198
199 if (preparedAttr.getRight().getValue() != null) {
200 values.addAll(preparedAttr.getRight().getValue());
201 }
202
203 attributes.add(AttributeBuilder.build(preparedAttr.getRight().getName(), values));
204 }
205 }
206 }
207
208 return connObjectKey;
209 }
210
211 protected static Name getName(final String evalConnObjectLink, final String connObjectKey) {
212
213
214 Name name;
215 if (StringUtils.isBlank(evalConnObjectLink)) {
216
217 LOG.debug("Add connObjectKey [{}] as {}", connObjectKey, Name.NAME);
218 name = new Name(connObjectKey);
219 } else {
220 LOG.debug("Add connObjectLink [{}] as {}", evalConnObjectLink, Name.NAME);
221 name = new Name(evalConnObjectLink);
222
223
224 LOG.debug("connObjectKey will be used just as {} attribute", Uid.NAME);
225 }
226
227 return name;
228 }
229
230
231
232
233
234
235
236
237
238
239
240 protected Name evaluateNAME(final Any<?> any, final Provision provision, final String connObjectKey) {
241 if (StringUtils.isBlank(connObjectKey)) {
242
243 LOG.warn("Missing ConnObjectKey value for {}: ", any.getType().getKey());
244 }
245
246
247 String connObjectLink = provision.getMapping() == null
248 ? null
249 : provision.getMapping().getConnObjectLink();
250 String evalConnObjectLink = null;
251 if (StringUtils.isNotBlank(connObjectLink)) {
252 JexlContext jexlContext = new MapContext();
253 JexlUtils.addFieldsToContext(any, jexlContext);
254 JexlUtils.addPlainAttrsToContext(any.getPlainAttrs(), jexlContext);
255 JexlUtils.addDerAttrsToContext(any, derAttrHandler, jexlContext);
256 evalConnObjectLink = JexlUtils.evaluate(connObjectLink, jexlContext).toString();
257 }
258
259 return getName(evalConnObjectLink, connObjectKey);
260 }
261
262
263
264
265
266
267
268
269
270
271
272 protected Name evaluateNAME(final Realm realm, final OrgUnit orgUnit, final String connObjectKey) {
273 if (StringUtils.isBlank(connObjectKey)) {
274
275 LOG.warn("Missing ConnObjectKey value for Realms");
276 }
277
278
279 String connObjectLink = orgUnit.getConnObjectLink();
280 String evalConnObjectLink = null;
281 if (StringUtils.isNotBlank(connObjectLink)) {
282 JexlContext jexlContext = new MapContext();
283 JexlUtils.addFieldsToContext(realm, jexlContext);
284 evalConnObjectLink = JexlUtils.evaluate(connObjectLink, jexlContext).toString();
285 }
286
287 return getName(evalConnObjectLink, connObjectKey);
288 }
289
290 @Transactional(readOnly = true)
291 @Override
292 public Pair<String, Set<Attribute>> prepareAttrsFromAny(
293 final Any<?> any,
294 final String password,
295 final boolean changePwd,
296 final Boolean enable,
297 final ExternalResource resource,
298 final Provision provision) {
299
300 LOG.debug("Preparing resource attributes for {} with provision {} for attributes {}",
301 any, provision, any.getPlainAttrs());
302
303 Set<Attribute> attributes = new HashSet<>();
304 String[] connObjectKeyValue = new String[1];
305
306 MappingUtils.getPropagationItems(provision.getMapping().getItems().stream()).forEach(mapItem -> {
307 LOG.debug("Processing expression '{}'", mapItem.getIntAttrName());
308
309 try {
310 String processedConnObjectKeyValue = processPreparedAttr(
311 prepareAttr(
312 resource,
313 provision,
314 mapItem,
315 any,
316 password,
317 AccountGetter.DEFAULT,
318 AccountGetter.DEFAULT,
319 PlainAttrGetter.DEFAULT),
320 attributes);
321 if (processedConnObjectKeyValue != null) {
322 connObjectKeyValue[0] = processedConnObjectKeyValue;
323 }
324 } catch (Exception e) {
325 LOG.error("Expression '{}' processing failed", mapItem.getIntAttrName(), e);
326 }
327 });
328
329 MappingUtils.getConnObjectKeyItem(provision).ifPresent(connObjectKeyItem -> {
330 Attribute connObjectKeyAttr = AttributeUtil.find(connObjectKeyItem.getExtAttrName(), attributes);
331 if (connObjectKeyAttr != null) {
332 attributes.remove(connObjectKeyAttr);
333 attributes.add(AttributeBuilder.build(connObjectKeyItem.getExtAttrName(), connObjectKeyValue[0]));
334 }
335 Name name = evaluateNAME(any, provision, connObjectKeyValue[0]);
336 attributes.add(name);
337 if (connObjectKeyAttr == null
338 && connObjectKeyValue[0] != null && !connObjectKeyValue[0].equals(name.getNameValue())) {
339
340 attributes.add(AttributeBuilder.build(connObjectKeyItem.getExtAttrName(), connObjectKeyValue[0]));
341 }
342 });
343
344 if (enable != null) {
345 attributes.add(AttributeBuilder.buildEnabled(enable));
346 }
347 if (!changePwd) {
348 Attribute pwdAttr = AttributeUtil.find(OperationalAttributes.PASSWORD_NAME, attributes);
349 if (pwdAttr != null) {
350 attributes.remove(pwdAttr);
351 }
352 }
353
354 return Pair.of(connObjectKeyValue[0], attributes);
355 }
356
357 @Transactional(readOnly = true)
358 @Override
359 public Set<Attribute> prepareAttrsFromLinkedAccount(
360 final User user,
361 final LinkedAccount account,
362 final String password,
363 final boolean changePwd,
364 final Provision provision) {
365
366 LOG.debug("Preparing resource attributes for linked account {} of user {} with provision {} "
367 + "for user attributes {} with override {}",
368 account, user, provision, user.getPlainAttrs(), account.getPlainAttrs());
369
370 Set<Attribute> attributes = new HashSet<>();
371
372 MappingUtils.getPropagationItems(provision.getMapping().getItems().stream()).forEach(mapItem -> {
373 LOG.debug("Processing expression '{}'", mapItem.getIntAttrName());
374
375 try {
376 processPreparedAttr(
377 prepareAttr(
378 account.getResource(),
379 provision,
380 mapItem,
381 user,
382 password,
383 acct -> account.getUsername() == null ? AccountGetter.DEFAULT.apply(acct) : account,
384 acct -> account.getPassword() == null ? AccountGetter.DEFAULT.apply(acct) : account,
385 (attributable, schema) -> {
386 PlainAttr<?> result = null;
387 if (attributable instanceof User) {
388 Optional<? extends LAPlainAttr> accountAttr = account.getPlainAttr(schema);
389 if (accountAttr.isPresent()) {
390 result = accountAttr.get();
391 }
392 }
393 if (result == null) {
394 result = PlainAttrGetter.DEFAULT.apply(attributable, schema);
395 }
396 return result;
397 }),
398 attributes);
399 } catch (Exception e) {
400 LOG.error("Expression '{}' processing failed", mapItem.getIntAttrName(), e);
401 }
402 });
403
404 String connObjectKey = account.getConnObjectKeyValue();
405 MappingUtils.getConnObjectKeyItem(provision).ifPresent(connObjectKeyItem -> {
406 Attribute connObjectKeyExtAttr = AttributeUtil.find(connObjectKeyItem.getExtAttrName(), attributes);
407 if (connObjectKeyExtAttr != null) {
408 attributes.remove(connObjectKeyExtAttr);
409 attributes.add(AttributeBuilder.build(connObjectKeyItem.getExtAttrName(), connObjectKey));
410 }
411 Name name = evaluateNAME(user, provision, connObjectKey);
412 attributes.add(name);
413 if (!connObjectKey.equals(name.getNameValue()) && connObjectKeyExtAttr == null) {
414 attributes.add(AttributeBuilder.build(connObjectKeyItem.getExtAttrName(), connObjectKey));
415 }
416 });
417
418 if (account.isSuspended() != null) {
419 attributes.add(AttributeBuilder.buildEnabled(BooleanUtils.negate(account.isSuspended())));
420 }
421 if (!changePwd) {
422 Attribute pwdAttr = AttributeUtil.find(OperationalAttributes.PASSWORD_NAME, attributes);
423 if (pwdAttr != null) {
424 attributes.remove(pwdAttr);
425 }
426 }
427
428 return attributes;
429 }
430
431 protected String getIntValue(final Realm realm, final Item orgUnitItem) {
432 String value = null;
433 switch (orgUnitItem.getIntAttrName()) {
434 case "key":
435 value = realm.getKey();
436 break;
437
438 case "name":
439 value = realm.getName();
440 break;
441
442 case "fullpath":
443 value = realm.getFullPath();
444 break;
445
446 default:
447 }
448
449 return value;
450 }
451
452 @Override
453 public Pair<String, Set<Attribute>> prepareAttrsFromRealm(final Realm realm, final OrgUnit orgUnit) {
454 LOG.debug("Preparing resource attributes for {} with orgUnit {}", realm, orgUnit);
455
456 Set<Attribute> attributes = new HashSet<>();
457 String[] connObjectKeyValue = new String[1];
458
459 MappingUtils.getPropagationItems(orgUnit.getItems().stream()).forEach(orgUnitItem -> {
460 LOG.debug("Processing expression '{}'", orgUnitItem.getIntAttrName());
461
462 String value = getIntValue(realm, orgUnitItem);
463
464 if (orgUnitItem.isConnObjectKey()) {
465 connObjectKeyValue[0] = value;
466 }
467
468 Attribute alreadyAdded = AttributeUtil.find(orgUnitItem.getExtAttrName(), attributes);
469 if (alreadyAdded == null) {
470 if (value == null) {
471 attributes.add(AttributeBuilder.build(orgUnitItem.getExtAttrName()));
472 } else {
473 attributes.add(AttributeBuilder.build(orgUnitItem.getExtAttrName(), value));
474 }
475 } else if (value != null) {
476 attributes.remove(alreadyAdded);
477
478 Set<Object> values = new HashSet<>();
479 if (alreadyAdded.getValue() != null && !alreadyAdded.getValue().isEmpty()) {
480 values.addAll(alreadyAdded.getValue());
481 }
482 values.add(value);
483
484 attributes.add(AttributeBuilder.build(orgUnitItem.getExtAttrName(), values));
485 }
486 });
487
488 Optional<Item> connObjectKeyItem = orgUnit.getConnObjectKeyItem();
489 if (connObjectKeyItem.isPresent()) {
490 Attribute connObjectKeyAttr = AttributeUtil.find(connObjectKeyItem.get().getExtAttrName(), attributes);
491 if (connObjectKeyAttr != null) {
492 attributes.remove(connObjectKeyAttr);
493 attributes.add(AttributeBuilder.build(connObjectKeyItem.get().getExtAttrName(), connObjectKeyValue[0]));
494 }
495 attributes.add(evaluateNAME(realm, orgUnit, connObjectKeyValue[0]));
496 }
497
498 return Pair.of(connObjectKeyValue[0], attributes);
499 }
500
501 protected String decodePassword(final Account account) {
502 try {
503 return ENCRYPTOR.decode(account.getPassword(), account.getCipherAlgorithm());
504 } catch (Exception e) {
505 LOG.error("Could not decode password for {}", account, e);
506 return null;
507 }
508 }
509
510 protected String getPasswordAttrValue(final Account account, final String defaultValue) {
511 String passwordAttrValue;
512 if (account instanceof LinkedAccount) {
513 if (account.getPassword() != null) {
514 passwordAttrValue = decodePassword(account);
515 } else {
516 passwordAttrValue = defaultValue;
517 }
518 } else {
519 if (StringUtils.isNotBlank(defaultValue)) {
520 passwordAttrValue = defaultValue;
521 } else if (account.canDecodeSecrets()) {
522 passwordAttrValue = decodePassword(account);
523 } else {
524 passwordAttrValue = null;
525 }
526 }
527
528 return passwordAttrValue;
529 }
530
531 @Override
532 public Pair<String, Attribute> prepareAttr(
533 final ExternalResource resource,
534 final Provision provision,
535 final Item item,
536 final Any<?> any,
537 final String password,
538 final AccountGetter usernameAccountGetter,
539 final AccountGetter passwordAccountGetter,
540 final PlainAttrGetter plainAttrGetter) {
541
542 IntAttrName intAttrName;
543 try {
544 intAttrName = intAttrNameParser.parse(item.getIntAttrName(), any.getType().getKind());
545 } catch (ParseException e) {
546 LOG.error("Invalid intAttrName '{}' specified, ignoring", item.getIntAttrName(), e);
547 return null;
548 }
549
550 AttrSchemaType schemaType = intAttrName.getSchema() instanceof PlainSchema
551 ? intAttrName.getSchema().getType()
552 : AttrSchemaType.String;
553 boolean readOnlyVirSchema = intAttrName.getSchema() instanceof VirSchema
554 ? intAttrName.getSchema().isReadonly()
555 : false;
556
557 Pair<AttrSchemaType, List<PlainAttrValue>> intValues = getIntValues(
558 resource, provision, item, intAttrName, schemaType, any, usernameAccountGetter, plainAttrGetter);
559 schemaType = intValues.getLeft();
560 List<PlainAttrValue> values = intValues.getRight();
561
562 LOG.debug("Define mapping for: "
563 + "\n* ExtAttrName " + item.getExtAttrName()
564 + "\n* is connObjectKey " + item.isConnObjectKey()
565 + "\n* is password " + item.isPassword()
566 + "\n* mandatory condition " + item.getMandatoryCondition()
567 + "\n* Schema " + intAttrName.getSchema()
568 + "\n* ClassType " + schemaType.getType().getName()
569 + "\n* AttrSchemaType " + schemaType
570 + "\n* Values " + values);
571
572 Pair<String, Attribute> result;
573 if (readOnlyVirSchema) {
574 result = null;
575 } else {
576 List<Object> objValues = new ArrayList<>();
577
578 for (PlainAttrValue value : values) {
579 if (FrameworkUtil.isSupportedAttributeType(schemaType.getType())) {
580 objValues.add(value.getValue());
581 } else {
582 PlainSchema plainSchema = intAttrName.getSchema() instanceof PlainSchema
583 ? (PlainSchema) intAttrName.getSchema()
584 : null;
585 if (plainSchema == null || plainSchema.getType() != schemaType) {
586 objValues.add(value.getValueAsString(schemaType));
587 } else {
588 objValues.add(value.getValueAsString(plainSchema));
589 }
590 }
591 }
592
593 if (item.isConnObjectKey()) {
594 result = Pair.of(objValues.isEmpty() ? null : objValues.iterator().next().toString(), null);
595 } else if (item.isPassword() && any instanceof User) {
596 String passwordAttrValue = getPasswordAttrValue(passwordAccountGetter.apply((User) any), password);
597 if (passwordAttrValue == null) {
598 result = null;
599 } else {
600 result = Pair.of(null, AttributeBuilder.buildPassword(passwordAttrValue.toCharArray()));
601 }
602 } else {
603 result = Pair.of(null, objValues.isEmpty()
604 ? AttributeBuilder.build(item.getExtAttrName())
605 : AttributeBuilder.build(item.getExtAttrName(), objValues));
606 }
607 }
608
609 return result;
610 }
611
612 @Transactional(readOnly = true)
613 @SuppressWarnings("unchecked")
614 @Override
615 public Pair<AttrSchemaType, List<PlainAttrValue>> getIntValues(
616 final ExternalResource resource,
617 final Provision provision,
618 final Item mapItem,
619 final IntAttrName intAttrName,
620 final AttrSchemaType schemaType,
621 final Any<?> any,
622 final AccountGetter usernameAccountGetter,
623 final PlainAttrGetter plainAttrGetter) {
624
625 LOG.debug("Get internal values for {} as '{}' on {}", any, mapItem.getIntAttrName(), resource);
626
627 List<Any<?>> references = new ArrayList<>();
628 Membership<?> membership = null;
629 if (intAttrName.getEnclosingGroup() == null
630 && intAttrName.getRelatedAnyObject() == null
631 && intAttrName.getRelationshipAnyType() == null
632 && intAttrName.getRelationshipType() == null
633 && intAttrName.getRelatedUser() == null) {
634 references.add(any);
635 }
636 if (any instanceof GroupableRelatable) {
637 GroupableRelatable<?, ?, ?, ?, ?> groupableRelatable = (GroupableRelatable<?, ?, ?, ?, ?>) any;
638
639 if (intAttrName.getEnclosingGroup() != null) {
640 Group group = groupDAO.findByName(intAttrName.getEnclosingGroup());
641 if (group == null
642 || any instanceof User
643 ? !userDAO.findAllGroupKeys((User) any).contains(group.getKey())
644 : any instanceof AnyObject
645 ? !anyObjectDAO.findAllGroupKeys((AnyObject) any).contains(group.getKey())
646 : false) {
647
648 LOG.warn("No (dyn) membership for {} in {}, ignoring",
649 intAttrName.getEnclosingGroup(), groupableRelatable);
650 } else {
651 references.add(group);
652 }
653 } else if (intAttrName.getRelatedUser() != null) {
654 User user = userDAO.findByUsername(intAttrName.getRelatedUser());
655 if (user == null || user.getRelationships(groupableRelatable.getKey()).isEmpty()) {
656 LOG.warn("No relationship for {} in {}, ignoring",
657 intAttrName.getRelatedUser(), groupableRelatable);
658 } else if (groupableRelatable.getType().getKind() == AnyTypeKind.USER) {
659 LOG.warn("Users cannot have relationship with other users, ignoring");
660 } else {
661 references.add(user);
662 }
663 } else if (intAttrName.getRelatedAnyObject() != null) {
664 AnyObject anyObject = anyObjectDAO.find(intAttrName.getRelatedAnyObject());
665 if (anyObject == null || groupableRelatable.getRelationships(anyObject.getKey()).isEmpty()) {
666 LOG.warn("No relationship for {} in {}, ignoring",
667 intAttrName.getRelatedAnyObject(), groupableRelatable);
668 } else {
669 references.add(anyObject);
670 }
671 } else if (intAttrName.getRelationshipAnyType() != null && intAttrName.getRelationshipType() != null) {
672 RelationshipType relationshipType = relationshipTypeDAO.find(intAttrName.getRelationshipType());
673 AnyType anyType = anyTypeDAO.find(intAttrName.getRelationshipAnyType());
674 if (relationshipType == null || groupableRelatable.getRelationships(relationshipType).isEmpty()) {
675 LOG.warn("No relationship for type {} in {}, ignoring",
676 intAttrName.getRelationshipType(), groupableRelatable);
677 } else if (anyType == null) {
678 LOG.warn("No anyType {}, ignoring", intAttrName.getRelationshipAnyType());
679 } else {
680 references.addAll(groupableRelatable.getRelationships(relationshipType).stream().
681 filter(relationship -> anyType.equals(relationship.getRightEnd().getType())).
682 map(Relationship::getRightEnd).
683 collect(Collectors.toList()));
684 }
685 } else if (intAttrName.getMembershipOfGroup() != null) {
686 Group group = groupDAO.findByName(intAttrName.getMembershipOfGroup());
687 membership = groupableRelatable.getMembership(group.getKey()).orElse(null);
688 }
689 }
690 if (references.isEmpty()) {
691 LOG.warn("Could not determine the reference instance for {}", mapItem.getIntAttrName());
692 return Pair.of(schemaType, List.of());
693 }
694
695 List<PlainAttrValue> values = new ArrayList<>();
696 boolean transform = true;
697
698 for (Any<?> ref : references) {
699 AnyUtils anyUtils = anyUtilsFactory.getInstance(ref);
700 if (intAttrName.getField() != null) {
701 PlainAttrValue attrValue = anyUtils.newPlainAttrValue();
702
703 switch (intAttrName.getField()) {
704 case "key":
705 attrValue.setStringValue(ref.getKey());
706 values.add(attrValue);
707 break;
708
709 case "username":
710 if (ref instanceof Account) {
711 attrValue.setStringValue(usernameAccountGetter.apply((Account) ref).getUsername());
712 values.add(attrValue);
713 }
714 break;
715
716 case "realm":
717 attrValue.setStringValue(ref.getRealm().getFullPath());
718 values.add(attrValue);
719 break;
720
721 case "password":
722
723 break;
724
725 case "userOwner":
726 case "groupOwner":
727 Mapping uMappingTO = provision.getAnyType().equals(AnyTypeKind.USER.name())
728 ? provision.getMapping()
729 : null;
730 Mapping gMappingTO = provision.getAnyType().equals(AnyTypeKind.GROUP.name())
731 ? provision.getMapping()
732 : null;
733
734 if (ref instanceof Group) {
735 Group group = (Group) ref;
736 String groupOwnerValue = null;
737 if (group.getUserOwner() != null && uMappingTO != null) {
738 groupOwnerValue = getGroupOwnerValue(resource, provision, group.getUserOwner());
739 }
740 if (group.getGroupOwner() != null && gMappingTO != null) {
741 groupOwnerValue = getGroupOwnerValue(resource, provision, group.getGroupOwner());
742 }
743
744 if (StringUtils.isNotBlank(groupOwnerValue)) {
745 attrValue.setStringValue(groupOwnerValue);
746 values.add(attrValue);
747 }
748 }
749 break;
750
751 case "suspended":
752 if (ref instanceof User) {
753 attrValue.setBooleanValue(((User) ref).isSuspended());
754 values.add(attrValue);
755 }
756 break;
757
758 case "mustChangePassword":
759 if (ref instanceof User) {
760 attrValue.setBooleanValue(((User) ref).isMustChangePassword());
761 values.add(attrValue);
762 }
763 break;
764
765 default:
766 try {
767 Object fieldValue = FieldUtils.readField(ref, intAttrName.getField(), true);
768 if (fieldValue instanceof TemporalAccessor) {
769
770 attrValue.setStringValue(FormatUtils.format((TemporalAccessor) fieldValue));
771 } else if (Boolean.TYPE.isInstance(fieldValue)) {
772 attrValue.setBooleanValue((Boolean) fieldValue);
773 } else if (Double.TYPE.isInstance(fieldValue) || Float.TYPE.isInstance(fieldValue)) {
774 attrValue.setDoubleValue((Double) fieldValue);
775 } else if (Long.TYPE.isInstance(fieldValue) || Integer.TYPE.isInstance(fieldValue)) {
776 attrValue.setLongValue((Long) fieldValue);
777 } else {
778 attrValue.setStringValue(fieldValue.toString());
779 }
780 values.add(attrValue);
781 } catch (Exception e) {
782 LOG.error("Could not read value of '{}' from {}", intAttrName.getField(), ref, e);
783 }
784 }
785 } else if (intAttrName.getSchemaType() != null) {
786 switch (intAttrName.getSchemaType()) {
787 case PLAIN:
788 PlainAttr<?> attr;
789 if (membership == null) {
790 attr = plainAttrGetter.apply((Attributable) ref, intAttrName.getSchema().getKey());
791 } else {
792 attr = ((GroupableRelatable<?, ?, ?, ?, ?>) ref).getPlainAttr(
793 intAttrName.getSchema().getKey(), membership).orElse(null);
794 }
795 if (attr != null) {
796 if (attr.getUniqueValue() != null) {
797 values.add(anyUtils.clonePlainAttrValue(attr.getUniqueValue()));
798 } else if (attr.getValues() != null) {
799 attr.getValues().forEach(value -> values.add(anyUtils.clonePlainAttrValue(value)));
800 }
801 }
802 break;
803
804 case DERIVED:
805 DerSchema derSchema = (DerSchema) intAttrName.getSchema();
806 String derValue = membership == null
807 ? derAttrHandler.getValue(ref, derSchema)
808 : derAttrHandler.getValue(ref, membership, derSchema);
809 if (derValue != null) {
810 PlainAttrValue attrValue = anyUtils.newPlainAttrValue();
811 attrValue.setStringValue(derValue);
812 values.add(attrValue);
813 }
814 break;
815
816 case VIRTUAL:
817
818 transform = false;
819
820 VirAttrCacheKey cacheKey = new VirAttrCacheKey(
821 ref.getType().getKey(), ref.getKey(), intAttrName.getSchema().getKey());
822 virAttrCache.expire(cacheKey);
823 LOG.debug("Evicted from cache: {}", cacheKey);
824
825 VirSchema virSchema = (VirSchema) intAttrName.getSchema();
826 List<String> virValues = membership == null
827 ? virAttrHandler.getValues(ref, virSchema)
828 : virAttrHandler.getValues(ref, membership, virSchema);
829 virValues.forEach(virValue -> {
830 PlainAttrValue attrValue = anyUtils.newPlainAttrValue();
831 attrValue.setStringValue(virValue);
832 values.add(attrValue);
833 });
834 break;
835
836 default:
837 }
838 } else if (intAttrName.getPrivilegesOfApplication() != null && ref instanceof User) {
839 Application application = applicationDAO.find(intAttrName.getPrivilegesOfApplication());
840 if (application == null) {
841 LOG.warn("Invalid application: {}", intAttrName.getPrivilegesOfApplication());
842 } else {
843 userDAO.findAllRoles((User) ref).stream().
844 flatMap(role -> role.getPrivileges(application).stream()).
845 forEach(privilege -> {
846 PlainAttrValue attrValue = anyUtils.newPlainAttrValue();
847 attrValue.setStringValue(privilege.getKey());
848 values.add(attrValue);
849 });
850 }
851 }
852 }
853
854 LOG.debug("Internal values: {}", values);
855
856 Pair<AttrSchemaType, List<PlainAttrValue>> transformed = Pair.of(schemaType, values);
857 if (transform) {
858 for (ItemTransformer transformer : MappingUtils.getItemTransformers(mapItem, getTransformers(mapItem))) {
859 transformed = transformer.beforePropagation(
860 mapItem, any, transformed.getLeft(), transformed.getRight());
861 }
862 LOG.debug("Transformed values: {}", values);
863 } else {
864 LOG.debug("No transformation occurred");
865 }
866
867 return transformed;
868 }
869
870 protected String getGroupOwnerValue(
871 final ExternalResource resource,
872 final Provision provision,
873 final Any<?> any) {
874
875 Optional<Item> connObjectKeyItem = MappingUtils.getConnObjectKeyItem(provision);
876
877 Pair<String, Attribute> preparedAttr = null;
878 if (connObjectKeyItem.isPresent()) {
879 preparedAttr = prepareAttr(
880 resource,
881 provision,
882 connObjectKeyItem.get(),
883 any,
884 null,
885 AccountGetter.DEFAULT,
886 AccountGetter.DEFAULT,
887 PlainAttrGetter.DEFAULT);
888 }
889
890 return Optional.ofNullable(preparedAttr).
891 map(attr -> evaluateNAME(any, provision, attr.getKey()).getNameValue()).orElse(null);
892 }
893
894 @Transactional(readOnly = true)
895 @Override
896 public Optional<String> getConnObjectKeyValue(
897 final Any<?> any,
898 final ExternalResource resource,
899 final Provision provision) {
900
901 Optional<Item> connObjectKeyItem = provision.getMapping().getConnObjectKeyItem();
902 if (connObjectKeyItem.isEmpty()) {
903 LOG.error("Unable to locate conn object key item for {}", any.getType().getKey());
904 return Optional.empty();
905 }
906 Item mapItem = connObjectKeyItem.get();
907 Pair<AttrSchemaType, List<PlainAttrValue>> intValues;
908 try {
909 intValues = getIntValues(
910 resource,
911 provision,
912 mapItem,
913 intAttrNameParser.parse(mapItem.getIntAttrName(), any.getType().getKind()),
914 AttrSchemaType.String,
915 any,
916 AccountGetter.DEFAULT,
917 PlainAttrGetter.DEFAULT);
918 } catch (ParseException e) {
919 LOG.error("Invalid intAttrName '{}' specified, ignoring", mapItem.getIntAttrName(), e);
920 intValues = Pair.of(AttrSchemaType.String, List.of());
921 }
922 return Optional.ofNullable(intValues.getRight().isEmpty()
923 ? null
924 : intValues.getRight().get(0).getValueAsString());
925 }
926
927 @Transactional(readOnly = true)
928 @Override
929 public Optional<String> getConnObjectKeyValue(final Realm realm, final OrgUnit orgUnit) {
930 Optional<Item> connObjectKeyItem = orgUnit.getConnObjectKeyItem();
931 if (connObjectKeyItem.isEmpty()) {
932 LOG.error("Unable to locate conn object key item for Realms");
933 return Optional.empty();
934 }
935 return Optional.ofNullable(getIntValue(realm, connObjectKeyItem.get()));
936 }
937
938 @Transactional(readOnly = true)
939 @Override
940 public void setIntValues(final Item mapItem, final Attribute attr, final AnyTO anyTO) {
941 List<Object> values = null;
942 if (attr != null) {
943 values = attr.getValue();
944 for (ItemTransformer transformer : MappingUtils.getItemTransformers(mapItem, getTransformers(mapItem))) {
945 values = transformer.beforePull(mapItem, anyTO, values);
946 }
947 }
948 values = Optional.ofNullable(values).orElse(List.of());
949
950 IntAttrName intAttrName;
951 try {
952 intAttrName = intAttrNameParser.parse(mapItem.getIntAttrName(), AnyTypeKind.fromTOClass(anyTO.getClass()));
953 } catch (ParseException e) {
954 LOG.error("Invalid intAttrName '{}' specified, ignoring", mapItem.getIntAttrName(), e);
955 return;
956 }
957
958 if (intAttrName.getField() != null) {
959 switch (intAttrName.getField()) {
960 case "password":
961 if (anyTO instanceof UserTO && !values.isEmpty()) {
962 ((UserTO) anyTO).setPassword(ConnObjectUtils.getPassword(values.get(0)));
963 }
964 break;
965
966 case "username":
967 if (anyTO instanceof UserTO) {
968 ((UserTO) anyTO).setUsername(values.isEmpty() || values.get(0) == null
969 ? null
970 : values.get(0).toString());
971 }
972 break;
973
974 case "name":
975 if (anyTO instanceof GroupTO) {
976 ((GroupTO) anyTO).setName(values.isEmpty() || values.get(0) == null
977 ? null
978 : values.get(0).toString());
979 } else if (anyTO instanceof AnyObjectTO) {
980 ((AnyObjectTO) anyTO).setName(values.isEmpty() || values.get(0) == null
981 ? null
982 : values.get(0).toString());
983 }
984 break;
985
986 case "mustChangePassword":
987 if (anyTO instanceof UserTO && !values.isEmpty() && values.get(0) != null) {
988 ((UserTO) anyTO).setMustChangePassword(BooleanUtils.toBoolean(values.get(0).toString()));
989 }
990 break;
991
992 case "userOwner":
993 case "groupOwner":
994 if (anyTO instanceof GroupTO && attr != null) {
995
996
997 Attr attrTO = new Attr();
998 attrTO.setSchema(StringUtils.EMPTY);
999 if (values.isEmpty() || values.get(0) == null) {
1000 attrTO.getValues().add(StringUtils.EMPTY);
1001 } else {
1002 attrTO.getValues().add(values.get(0).toString());
1003 }
1004
1005 ((GroupTO) anyTO).getPlainAttrs().add(attrTO);
1006 }
1007 break;
1008
1009 default:
1010 }
1011 } else if (intAttrName.getSchemaType() != null && attr != null) {
1012 GroupableRelatableTO groupableTO;
1013 Group group;
1014 if (anyTO instanceof GroupableRelatableTO && intAttrName.getMembershipOfGroup() != null) {
1015 groupableTO = (GroupableRelatableTO) anyTO;
1016 group = groupDAO.findByName(intAttrName.getMembershipOfGroup());
1017 } else {
1018 groupableTO = null;
1019 group = null;
1020 }
1021
1022 switch (intAttrName.getSchemaType()) {
1023 case PLAIN:
1024 Attr attrTO = new Attr();
1025 attrTO.setSchema(intAttrName.getSchema().getKey());
1026
1027 PlainSchema schema = (PlainSchema) intAttrName.getSchema();
1028
1029 for (Object value : values) {
1030 AttrSchemaType schemaType = schema == null ? AttrSchemaType.String : schema.getType();
1031 if (value != null) {
1032 if (schemaType == AttrSchemaType.Binary) {
1033 attrTO.getValues().add(Base64.getEncoder().encodeToString((byte[]) value));
1034 } else {
1035 attrTO.getValues().add(value.toString());
1036 }
1037 }
1038 }
1039
1040 if (groupableTO == null || group == null) {
1041 anyTO.getPlainAttrs().add(attrTO);
1042 } else {
1043 MembershipTO membership = groupableTO.getMembership(group.getKey()).orElseGet(() -> {
1044 MembershipTO newMemb = new MembershipTO.Builder(group.getKey()).build();
1045 groupableTO.getMemberships().add(newMemb);
1046 return newMemb;
1047 });
1048 membership.getPlainAttrs().add(attrTO);
1049 }
1050 break;
1051
1052 case DERIVED:
1053 attrTO = new Attr();
1054 attrTO.setSchema(intAttrName.getSchema().getKey());
1055
1056 if (groupableTO == null || group == null) {
1057 anyTO.getDerAttrs().add(attrTO);
1058 } else {
1059 MembershipTO membership = groupableTO.getMembership(group.getKey()).orElseGet(() -> {
1060 MembershipTO newMemb = new MembershipTO.Builder(group.getKey()).build();
1061 groupableTO.getMemberships().add(newMemb);
1062 return newMemb;
1063 });
1064 membership.getDerAttrs().add(attrTO);
1065 }
1066 break;
1067
1068 case VIRTUAL:
1069 attrTO = new Attr();
1070 attrTO.setSchema(intAttrName.getSchema().getKey());
1071
1072
1073 if (attr.getValue() != null && !attr.getValue().isEmpty()) {
1074 attr.getValue().stream().
1075 filter(Objects::nonNull).
1076 forEachOrdered(value -> attrTO.getValues().add(value.toString()));
1077 }
1078
1079 if (groupableTO == null || group == null) {
1080 anyTO.getVirAttrs().add(attrTO);
1081 } else {
1082 MembershipTO membership = groupableTO.getMembership(group.getKey()).orElseGet(() -> {
1083 MembershipTO newMemb = new MembershipTO.Builder(group.getKey()).build();
1084 groupableTO.getMemberships().add(newMemb);
1085 return newMemb;
1086 });
1087 membership.getVirAttrs().add(attrTO);
1088 }
1089 break;
1090
1091 default:
1092 }
1093 }
1094 }
1095
1096 @Override
1097 public void setIntValues(final Item item, final Attribute attr, final RealmTO realmTO) {
1098 List<Object> values = null;
1099 if (attr != null) {
1100 values = attr.getValue();
1101 for (ItemTransformer transformer : MappingUtils.getItemTransformers(item, getTransformers(item))) {
1102 values = transformer.beforePull(item, realmTO, values);
1103 }
1104 }
1105
1106 if (values != null && !values.isEmpty() && values.get(0) != null) {
1107 switch (item.getIntAttrName()) {
1108 case "name":
1109 realmTO.setName(values.get(0).toString());
1110 break;
1111
1112 case "fullpath":
1113 String parentFullPath = StringUtils.substringBeforeLast(values.get(0).toString(), "/");
1114 Realm parent = realmDAO.findByFullPath(parentFullPath);
1115 if (parent == null) {
1116 LOG.warn("Could not find Realm with path {}, ignoring", parentFullPath);
1117 } else {
1118 realmTO.setParent(parent.getFullPath());
1119 }
1120 break;
1121
1122 default:
1123 }
1124 }
1125 }
1126
1127 @Transactional(readOnly = true)
1128 @Override
1129 public boolean hasMustChangePassword(final Provision provision) {
1130 return provision != null && provision.getMapping() != null
1131 && provision.getMapping().getItems().stream().
1132 anyMatch(mappingItem -> "mustChangePassword".equals(mappingItem.getIntAttrName()));
1133 }
1134 }