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.util.ArrayList;
22 import java.util.Collection;
23 import java.util.HashMap;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Optional;
27 import java.util.Set;
28 import java.util.stream.Collectors;
29 import org.apache.commons.jexl3.MapContext;
30 import org.apache.commons.lang3.BooleanUtils;
31 import org.apache.commons.lang3.StringUtils;
32 import org.apache.commons.lang3.tuple.Pair;
33 import org.apache.syncope.common.lib.AnyOperations;
34 import org.apache.syncope.common.lib.Attr;
35 import org.apache.syncope.common.lib.EntityTOUtils;
36 import org.apache.syncope.common.lib.SyncopeConstants;
37 import org.apache.syncope.common.lib.request.AttrPatch;
38 import org.apache.syncope.common.lib.request.GroupCR;
39 import org.apache.syncope.common.lib.request.GroupUR;
40 import org.apache.syncope.common.lib.request.PasswordPatch;
41 import org.apache.syncope.common.lib.request.StatusR;
42 import org.apache.syncope.common.lib.request.StringReplacePatchItem;
43 import org.apache.syncope.common.lib.request.UserCR;
44 import org.apache.syncope.common.lib.request.UserUR;
45 import org.apache.syncope.common.lib.scim.SCIMComplexConf;
46 import org.apache.syncope.common.lib.scim.SCIMConf;
47 import org.apache.syncope.common.lib.scim.SCIMEnterpriseUserConf;
48 import org.apache.syncope.common.lib.scim.SCIMManagerConf;
49 import org.apache.syncope.common.lib.scim.SCIMUserAddressConf;
50 import org.apache.syncope.common.lib.to.GroupTO;
51 import org.apache.syncope.common.lib.to.MembershipTO;
52 import org.apache.syncope.common.lib.to.UserTO;
53 import org.apache.syncope.common.lib.types.PatchOperation;
54 import org.apache.syncope.common.lib.types.StatusRType;
55 import org.apache.syncope.core.logic.scim.SCIMConfManager;
56 import org.apache.syncope.core.persistence.api.dao.AnyDAO;
57 import org.apache.syncope.core.persistence.api.dao.search.MembershipCond;
58 import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
59 import org.apache.syncope.core.provisioning.api.jexl.JexlUtils;
60 import org.apache.syncope.core.spring.security.AuthDataAccessor;
61 import org.apache.syncope.ext.scimv2.api.BadRequestException;
62 import org.apache.syncope.ext.scimv2.api.data.Group;
63 import org.apache.syncope.ext.scimv2.api.data.Member;
64 import org.apache.syncope.ext.scimv2.api.data.Meta;
65 import org.apache.syncope.ext.scimv2.api.data.SCIMComplexValue;
66 import org.apache.syncope.ext.scimv2.api.data.SCIMEnterpriseInfo;
67 import org.apache.syncope.ext.scimv2.api.data.SCIMGroup;
68 import org.apache.syncope.ext.scimv2.api.data.SCIMPatchOperation;
69 import org.apache.syncope.ext.scimv2.api.data.SCIMUser;
70 import org.apache.syncope.ext.scimv2.api.data.SCIMUserAddress;
71 import org.apache.syncope.ext.scimv2.api.data.SCIMUserManager;
72 import org.apache.syncope.ext.scimv2.api.data.SCIMUserName;
73 import org.apache.syncope.ext.scimv2.api.data.Value;
74 import org.apache.syncope.ext.scimv2.api.type.ErrorType;
75 import org.apache.syncope.ext.scimv2.api.type.Function;
76 import org.apache.syncope.ext.scimv2.api.type.PatchOp;
77 import org.apache.syncope.ext.scimv2.api.type.Resource;
78 import org.slf4j.Logger;
79 import org.slf4j.LoggerFactory;
80 import org.springframework.util.CollectionUtils;
81
82 public class SCIMDataBinder {
83
84 protected static final Logger LOG = LoggerFactory.getLogger(SCIMDataBinder.class);
85
86 protected static final List<String> USER_SCHEMAS = List.of(Resource.User.schema());
87
88 protected static final List<String> ENTERPRISE_USER_SCHEMAS =
89 List.of(Resource.User.schema(), Resource.EnterpriseUser.schema());
90
91 protected static final List<String> GROUP_SCHEMAS = List.of(Resource.Group.schema());
92
93
94
95
96
97
98
99 public static String filter2JexlExpression(final String filter) {
100 String jexlExpression = filter.
101 replace(" co ", " =~ ").
102 replace(" sw ", " =^ ").
103 replace(" ew ", " =$ ");
104
105 boolean endsWithPR = jexlExpression.endsWith(" pr");
106 int pr = endsWithPR ? jexlExpression.indexOf(" pr") : jexlExpression.indexOf(" pr ");
107 while (pr != -1) {
108 String before = jexlExpression.substring(0, pr);
109 int start = before.indexOf(' ') == -1 ? 0 : jexlExpression.substring(0, pr).lastIndexOf(' ', pr) + 1;
110 String literal = jexlExpression.substring(start, pr);
111
112 endsWithPR = jexlExpression.endsWith(" pr");
113 jexlExpression = jexlExpression.replace(
114 literal + " pr" + (endsWithPR ? "" : " "),
115 "not(empty(" + literal + "))" + (endsWithPR ? "" : " "));
116
117 pr = endsWithPR ? jexlExpression.indexOf(" pr") : jexlExpression.indexOf(" pr ");
118 }
119
120 return jexlExpression;
121 }
122
123 protected final SCIMConfManager confManager;
124
125 protected final UserLogic userLogic;
126
127 protected final AuthDataAccessor authDataAccessor;
128
129 public SCIMDataBinder(
130 final SCIMConfManager confManager,
131 final UserLogic userLogic,
132 final AuthDataAccessor authDataAccessor) {
133
134 this.confManager = confManager;
135 this.userLogic = userLogic;
136 this.authDataAccessor = authDataAccessor;
137 }
138
139 protected <E extends Enum<?>> void fill(
140 final Map<String, Attr> attrs,
141 final List<SCIMComplexConf<E>> confs,
142 final List<SCIMComplexValue> values) {
143
144 confs.forEach(conf -> {
145 SCIMComplexValue value = new SCIMComplexValue();
146
147 if (conf.getValue() != null && attrs.containsKey(conf.getValue())) {
148 value.setValue(attrs.get(conf.getValue()).getValues().get(0));
149 }
150 if (conf.getDisplay() != null && attrs.containsKey(conf.getDisplay())) {
151 value.setDisplay(attrs.get(conf.getDisplay()).getValues().get(0));
152 }
153 if (conf.getType() != null) {
154 value.setType(conf.getType().name());
155 }
156
157 value.setPrimary(conf.isPrimary());
158
159 if (!value.isEmpty()) {
160 values.add(value);
161 }
162 });
163 }
164
165 protected boolean output(
166 final List<String> attributes,
167 final List<String> excludedAttributes,
168 final String schema) {
169
170 return (attributes.isEmpty() || attributes.contains(schema))
171 && (excludedAttributes.isEmpty() || !excludedAttributes.contains(schema));
172 }
173
174 protected <T> T output(
175 final List<String> attributes,
176 final List<String> excludedAttributes,
177 final String schema,
178 final T value) {
179
180 return output(attributes, excludedAttributes, schema)
181 ? value
182 : null;
183 }
184
185 public SCIMUser toSCIMUser(
186 final UserTO userTO,
187 final String location,
188 final List<String> attributes,
189 final List<String> excludedAttributes) {
190
191 SCIMConf conf = confManager.get();
192
193 List<String> schemas = new ArrayList<>();
194 schemas.add(Resource.User.schema());
195 if (conf.getEnterpriseUserConf() != null) {
196 schemas.add(Resource.EnterpriseUser.schema());
197 }
198
199 SCIMUser user = new SCIMUser(
200 userTO.getKey(),
201 schemas,
202 new Meta(
203 Resource.User,
204 userTO.getCreationDate(),
205 Optional.ofNullable(userTO.getLastChangeDate()).orElse(userTO.getCreationDate()),
206 userTO.getETagValue(),
207 location),
208 output(attributes, excludedAttributes, "userName", userTO.getUsername()),
209 !userTO.isSuspended());
210
211 Map<String, Attr> attrs = new HashMap<>();
212 attrs.putAll(EntityTOUtils.buildAttrMap(userTO.getPlainAttrs()));
213 attrs.putAll(EntityTOUtils.buildAttrMap(userTO.getDerAttrs()));
214 attrs.putAll(EntityTOUtils.buildAttrMap(userTO.getVirAttrs()));
215 attrs.put("username", new Attr.Builder("username").value(userTO.getUsername()).build());
216
217 if (conf.getUserConf() != null) {
218 if (output(attributes, excludedAttributes, "externalId")
219 && conf.getUserConf().getExternalId() != null
220 && attrs.containsKey(conf.getUserConf().getExternalId())) {
221
222 user.setExternalId(attrs.get(conf.getUserConf().getExternalId()).getValues().get(0));
223 }
224
225 if (output(attributes, excludedAttributes, "name") && conf.getUserConf().getName() != null) {
226 SCIMUserName name = new SCIMUserName();
227
228 if (conf.getUserConf().getName().getFamilyName() != null
229 && attrs.containsKey(conf.getUserConf().getName().getFamilyName())) {
230
231 name.setFamilyName(attrs.get(conf.getUserConf().getName().getFamilyName()).getValues().get(0));
232 }
233 if (conf.getUserConf().getName().getFormatted() != null
234 && attrs.containsKey(conf.getUserConf().getName().getFormatted())) {
235
236 name.setFormatted(attrs.get(conf.getUserConf().getName().getFormatted()).getValues().get(0));
237 }
238 if (conf.getUserConf().getName().getGivenName() != null
239 && attrs.containsKey(conf.getUserConf().getName().getGivenName())) {
240
241 name.setGivenName(attrs.get(conf.getUserConf().getName().getGivenName()).getValues().get(0));
242 }
243 if (conf.getUserConf().getName().getHonorificPrefix() != null
244 && attrs.containsKey(conf.getUserConf().getName().getHonorificPrefix())) {
245
246 name.setHonorificPrefix(
247 attrs.get(conf.getUserConf().getName().getHonorificPrefix()).getValues().get(0));
248 }
249 if (conf.getUserConf().getName().getHonorificSuffix() != null
250 && attrs.containsKey(conf.getUserConf().getName().getHonorificSuffix())) {
251
252 name.setHonorificSuffix(
253 attrs.get(conf.getUserConf().getName().getHonorificSuffix()).getValues().get(0));
254 }
255 if (conf.getUserConf().getName().getMiddleName() != null
256 && attrs.containsKey(conf.getUserConf().getName().getMiddleName())) {
257
258 name.setMiddleName(attrs.get(conf.getUserConf().getName().getMiddleName()).getValues().get(0));
259 }
260
261 if (!name.isEmpty()) {
262 user.setName(name);
263 }
264 }
265
266 if (output(attributes, excludedAttributes, "displayName")
267 && conf.getUserConf().getDisplayName() != null
268 && attrs.containsKey(conf.getUserConf().getDisplayName())) {
269
270 user.setDisplayName(attrs.get(conf.getUserConf().getDisplayName()).getValues().get(0));
271 }
272 if (output(attributes, excludedAttributes, "nickName")
273 && conf.getUserConf().getNickName() != null
274 && attrs.containsKey(conf.getUserConf().getNickName())) {
275
276 user.setNickName(attrs.get(conf.getUserConf().getNickName()).getValues().get(0));
277 }
278 if (output(attributes, excludedAttributes, "profileUrl")
279 && conf.getUserConf().getProfileUrl() != null
280 && attrs.containsKey(conf.getUserConf().getProfileUrl())) {
281
282 user.setProfileUrl(attrs.get(conf.getUserConf().getProfileUrl()).getValues().get(0));
283 }
284 if (output(attributes, excludedAttributes, "title")
285 && conf.getUserConf().getTitle() != null
286 && attrs.containsKey(conf.getUserConf().getTitle())) {
287
288 user.setTitle(attrs.get(conf.getUserConf().getTitle()).getValues().get(0));
289 }
290 if (output(attributes, excludedAttributes, "userType")
291 && conf.getUserConf().getUserType() != null
292 && attrs.containsKey(conf.getUserConf().getUserType())) {
293
294 user.setUserType(attrs.get(conf.getUserConf().getUserType()).getValues().get(0));
295 }
296 if (output(attributes, excludedAttributes, "preferredLanguage")
297 && conf.getUserConf().getPreferredLanguage() != null
298 && attrs.containsKey(conf.getUserConf().getPreferredLanguage())) {
299
300 user.setPreferredLanguage(attrs.get(conf.getUserConf().getPreferredLanguage()).getValues().get(0));
301 }
302 if (output(attributes, excludedAttributes, "locale")
303 && conf.getUserConf().getLocale() != null
304 && attrs.containsKey(conf.getUserConf().getLocale())) {
305
306 user.setLocale(attrs.get(conf.getUserConf().getLocale()).getValues().get(0));
307 }
308 if (output(attributes, excludedAttributes, "timezone")
309 && conf.getUserConf().getTimezone() != null
310 && attrs.containsKey(conf.getUserConf().getTimezone())) {
311
312 user.setTimezone(attrs.get(conf.getUserConf().getTimezone()).getValues().get(0));
313 }
314
315 if (output(attributes, excludedAttributes, "emails")) {
316 fill(attrs, conf.getUserConf().getEmails(), user.getEmails());
317 }
318 if (output(attributes, excludedAttributes, "phoneNumbers")) {
319 fill(attrs, conf.getUserConf().getPhoneNumbers(), user.getPhoneNumbers());
320 }
321 if (output(attributes, excludedAttributes, "ims")) {
322 fill(attrs, conf.getUserConf().getIms(), user.getIms());
323 }
324 if (output(attributes, excludedAttributes, "photos")) {
325 fill(attrs, conf.getUserConf().getPhotos(), user.getPhotos());
326 }
327 if (output(attributes, excludedAttributes, "addresses")) {
328 conf.getUserConf().getAddresses().forEach(addressConf -> {
329 SCIMUserAddress address = new SCIMUserAddress();
330
331 if (addressConf.getFormatted() != null && attrs.containsKey(addressConf.getFormatted())) {
332 address.setFormatted(attrs.get(addressConf.getFormatted()).getValues().get(0));
333 }
334 if (addressConf.getStreetAddress() != null && attrs.containsKey(addressConf.getStreetAddress())) {
335 address.setStreetAddress(attrs.get(addressConf.getStreetAddress()).getValues().get(0));
336 }
337 if (addressConf.getLocality() != null && attrs.containsKey(addressConf.getLocality())) {
338 address.setLocality(attrs.get(addressConf.getLocality()).getValues().get(0));
339 }
340 if (addressConf.getRegion() != null && attrs.containsKey(addressConf.getRegion())) {
341 address.setRegion(attrs.get(addressConf.getRegion()).getValues().get(0));
342 }
343 if (addressConf.getCountry() != null && attrs.containsKey(addressConf.getCountry())) {
344 address.setCountry(attrs.get(addressConf.getCountry()).getValues().get(0));
345 }
346 if (addressConf.getType() != null) {
347 address.setType(addressConf.getType().name());
348 }
349 if (addressConf.isPrimary()) {
350 address.setPrimary(true);
351 }
352
353 if (!address.isEmpty()) {
354 user.getAddresses().add(address);
355 }
356 });
357 }
358 if (output(attributes, excludedAttributes, "x509Certificates")) {
359 conf.getUserConf().getX509Certificates().stream().filter(attrs::containsKey).
360 forEach(cert -> user.getX509Certificates().add(new Value(attrs.get(cert).getValues().get(0))));
361 }
362 }
363
364 if (conf.getEnterpriseUserConf() != null) {
365 SCIMEnterpriseInfo enterpriseInfo = new SCIMEnterpriseInfo();
366
367 if (output(attributes, excludedAttributes, "employeeNumber")
368 && conf.getEnterpriseUserConf().getEmployeeNumber() != null
369 && attrs.containsKey(conf.getEnterpriseUserConf().getEmployeeNumber())) {
370
371 enterpriseInfo.setEmployeeNumber(
372 attrs.get(conf.getEnterpriseUserConf().getEmployeeNumber()).getValues().get(0));
373 }
374 if (output(attributes, excludedAttributes, "costCenter")
375 && conf.getEnterpriseUserConf().getCostCenter() != null
376 && attrs.containsKey(conf.getEnterpriseUserConf().getCostCenter())) {
377
378 enterpriseInfo.setCostCenter(
379 attrs.get(conf.getEnterpriseUserConf().getCostCenter()).getValues().get(0));
380 }
381 if (output(attributes, excludedAttributes, "organization")
382 && conf.getEnterpriseUserConf().getOrganization() != null
383 && attrs.containsKey(conf.getEnterpriseUserConf().getOrganization())) {
384
385 enterpriseInfo.setOrganization(
386 attrs.get(conf.getEnterpriseUserConf().getOrganization()).getValues().get(0));
387 }
388 if (output(attributes, excludedAttributes, "division")
389 && conf.getEnterpriseUserConf().getDivision() != null
390 && attrs.containsKey(conf.getEnterpriseUserConf().getDivision())) {
391
392 enterpriseInfo.setDivision(
393 attrs.get(conf.getEnterpriseUserConf().getDivision()).getValues().get(0));
394 }
395 if (output(attributes, excludedAttributes, "department")
396 && conf.getEnterpriseUserConf().getDepartment() != null
397 && attrs.containsKey(conf.getEnterpriseUserConf().getDepartment())) {
398
399 enterpriseInfo.setDepartment(
400 attrs.get(conf.getEnterpriseUserConf().getDepartment()).getValues().get(0));
401 }
402 if (output(attributes, excludedAttributes, "manager")
403 && conf.getEnterpriseUserConf().getManager() != null) {
404
405 SCIMUserManager manager = new SCIMUserManager();
406
407 if (conf.getEnterpriseUserConf().getManager().getKey() != null
408 && attrs.containsKey(conf.getEnterpriseUserConf().getManager().getKey())) {
409
410 try {
411 UserTO userManager = userLogic.read(attrs.get(
412 conf.getEnterpriseUserConf().getManager().getKey()).getValues().get(0));
413 manager.setValue(userManager.getKey());
414 manager.setRef(
415 StringUtils.substringBefore(location, "/Users") + "/Users/" + userManager.getKey());
416
417 if (conf.getEnterpriseUserConf().getManager().getDisplayName() != null) {
418 Attr displayName = userManager.getPlainAttr(
419 conf.getEnterpriseUserConf().getManager().getDisplayName()).orElse(null);
420 if (displayName == null) {
421 displayName = userManager.getDerAttr(
422 conf.getEnterpriseUserConf().getManager().getDisplayName()).orElse(null);
423 }
424 if (displayName == null) {
425 displayName = userManager.getVirAttr(
426 conf.getEnterpriseUserConf().getManager().getDisplayName()).orElse(null);
427 }
428 if (displayName != null) {
429 manager.setDisplayName(displayName.getValues().get(0));
430 }
431 }
432 } catch (Exception e) {
433 LOG.error("Could not read user {}", conf.getEnterpriseUserConf().getManager().getKey(), e);
434 }
435 }
436
437 if (!manager.isEmpty()) {
438 enterpriseInfo.setManager(manager);
439 }
440 }
441
442 if (!enterpriseInfo.isEmpty()) {
443 user.setEnterpriseInfo(enterpriseInfo);
444 }
445 }
446
447 if (output(attributes, excludedAttributes, "groups")) {
448 userTO.getMemberships().forEach(membership -> user.getGroups().add(new Group(
449 membership.getGroupKey(),
450 StringUtils.substringBefore(location, "/Users") + "/Groups/" + membership.getGroupKey(),
451 membership.getGroupName(),
452 Function.direct)));
453 userTO.getDynMemberships().forEach(membership -> user.getGroups().add(new Group(
454 membership.getGroupKey(),
455 StringUtils.substringBefore(location, "/Users") + "/Groups/" + membership.getGroupKey(),
456 membership.getGroupName(),
457 Function.indirect)));
458 }
459
460 if (output(attributes, excludedAttributes, "entitlements")) {
461 authDataAccessor.getAuthorities(userTO.getUsername(), null).forEach(authority -> user.getEntitlements().
462 add(new Value(authority.getAuthority() + " on Realm(s) " + authority.getRealms())));
463 }
464
465 if (output(attributes, excludedAttributes, "roles")) {
466 userTO.getRoles().forEach(role -> user.getRoles().add(new Value(role)));
467 }
468
469 return user;
470 }
471
472 protected void setAttribute(
473 final UserTO userTO,
474 final String schema,
475 final String value) {
476
477 if (schema == null || value == null) {
478 return;
479 }
480
481 switch (schema) {
482 case "username":
483 userTO.setUsername(value);
484 break;
485
486 default:
487 userTO.getPlainAttrs().add(new Attr.Builder(schema).value(value).build());
488 }
489 }
490
491 protected <E extends Enum<?>> void setAttribute(
492 final Set<Attr> attrs,
493 final List<SCIMComplexConf<E>> confs,
494 final List<SCIMComplexValue> values) {
495
496 values.stream().filter(value -> value.getType() != null).forEach(value -> confs.stream().
497 filter(object -> value.getType().equals(object.getType().name())).findFirst().
498 ifPresent(conf -> attrs.add(
499 new Attr.Builder(conf.getValue()).value(value.getValue()).build())));
500 }
501
502 public UserTO toUserTO(final SCIMUser user, final boolean checkSchemas) {
503 if (checkSchemas
504 && !USER_SCHEMAS.equals(user.getSchemas())
505 && !ENTERPRISE_USER_SCHEMAS.equals(user.getSchemas())) {
506
507 throw new BadRequestException(ErrorType.invalidValue);
508 }
509
510 UserTO userTO = new UserTO();
511 userTO.setRealm(SyncopeConstants.ROOT_REALM);
512 userTO.setKey(user.getId());
513 userTO.setPassword(user.getPassword());
514 userTO.setUsername(user.getUserName());
515
516 SCIMConf conf = confManager.get();
517
518 if (conf.getUserConf() != null) {
519 setAttribute(
520 userTO,
521 conf.getUserConf().getExternalId(),
522 user.getExternalId());
523
524 if (conf.getUserConf().getName() != null && user.getName() != null) {
525 setAttribute(
526 userTO,
527 conf.getUserConf().getName().getFamilyName(),
528 user.getName().getFamilyName());
529
530 setAttribute(
531 userTO,
532 conf.getUserConf().getName().getFormatted(),
533 user.getName().getFormatted());
534
535 setAttribute(
536 userTO,
537 conf.getUserConf().getName().getGivenName(),
538 user.getName().getGivenName());
539
540 setAttribute(
541 userTO,
542 conf.getUserConf().getName().getHonorificPrefix(),
543 user.getName().getHonorificPrefix());
544
545 setAttribute(
546 userTO,
547 conf.getUserConf().getName().getHonorificSuffix(),
548 user.getName().getHonorificSuffix());
549
550 setAttribute(
551 userTO,
552 conf.getUserConf().getName().getMiddleName(),
553 user.getName().getMiddleName());
554 }
555
556 setAttribute(
557 userTO,
558 conf.getUserConf().getDisplayName(),
559 user.getDisplayName());
560
561 setAttribute(
562 userTO,
563 conf.getUserConf().getNickName(),
564 user.getNickName());
565
566 setAttribute(
567 userTO,
568 conf.getUserConf().getProfileUrl(),
569 user.getProfileUrl());
570
571 setAttribute(
572 userTO,
573 conf.getUserConf().getTitle(),
574 user.getTitle());
575
576 setAttribute(
577 userTO,
578 conf.getUserConf().getUserType(),
579 user.getUserType());
580
581 setAttribute(
582 userTO,
583 conf.getUserConf().getPreferredLanguage(),
584 user.getPreferredLanguage());
585
586 setAttribute(
587 userTO,
588 conf.getUserConf().getLocale(),
589 user.getLocale());
590
591 setAttribute(
592 userTO,
593 conf.getUserConf().getTimezone(),
594 user.getTimezone());
595
596 setAttribute(userTO.getPlainAttrs(), conf.getUserConf().getEmails(), user.getEmails());
597 setAttribute(userTO.getPlainAttrs(), conf.getUserConf().getPhoneNumbers(), user.getPhoneNumbers());
598 setAttribute(userTO.getPlainAttrs(), conf.getUserConf().getIms(), user.getIms());
599 setAttribute(userTO.getPlainAttrs(), conf.getUserConf().getPhotos(), user.getPhotos());
600
601 user.getAddresses().stream().filter(address -> address.getType() != null).
602 forEach(address -> conf.getUserConf().getAddresses().stream().
603 filter(object -> address.getType().equals(object.getType().name())).findFirst().
604 ifPresent(addressConf -> {
605 setAttribute(
606 userTO,
607 addressConf.getFormatted(),
608 address.getFormatted());
609
610 setAttribute(
611 userTO,
612 addressConf.getStreetAddress(),
613 address.getStreetAddress());
614
615 setAttribute(
616 userTO,
617 addressConf.getLocality(),
618 address.getLocality());
619
620 setAttribute(
621 userTO,
622 addressConf.getRegion(),
623 address.getRegion());
624
625 setAttribute(
626 userTO,
627 addressConf.getPostalCode(),
628 address.getPostalCode());
629
630 setAttribute(
631 userTO,
632 addressConf.getCountry(),
633 address.getCountry());
634 }));
635
636 for (int i = 0; i < user.getX509Certificates().size(); i++) {
637 Value certificate = user.getX509Certificates().get(i);
638 if (conf.getUserConf().getX509Certificates().size() > i) {
639 setAttribute(
640 userTO,
641 conf.getUserConf().getX509Certificates().get(i),
642 certificate.getValue());
643 }
644 }
645 }
646
647 if (conf.getEnterpriseUserConf() != null && user.getEnterpriseInfo() != null) {
648 setAttribute(
649 userTO,
650 conf.getEnterpriseUserConf().getEmployeeNumber(),
651 user.getEnterpriseInfo().getEmployeeNumber());
652
653 setAttribute(
654 userTO,
655 conf.getEnterpriseUserConf().getCostCenter(),
656 user.getEnterpriseInfo().getCostCenter());
657
658 setAttribute(
659 userTO,
660 conf.getEnterpriseUserConf().getOrganization(),
661 user.getEnterpriseInfo().getOrganization());
662
663 setAttribute(
664 userTO,
665 conf.getEnterpriseUserConf().getDivision(),
666 user.getEnterpriseInfo().getDivision());
667
668 setAttribute(
669 userTO,
670 conf.getEnterpriseUserConf().getDepartment(),
671 user.getEnterpriseInfo().getDepartment());
672
673 setAttribute(
674 userTO,
675 Optional.ofNullable(conf.getEnterpriseUserConf().getManager()).
676 map(SCIMManagerConf::getKey).orElse(null),
677 Optional.ofNullable(user.getEnterpriseInfo().getManager()).
678 map(SCIMUserManager::getValue).orElse(null));
679 }
680
681 userTO.getMemberships().addAll(user.getGroups().stream().
682 map(group -> new MembershipTO.Builder(group.getValue()).build()).
683 collect(Collectors.toList()));
684
685 userTO.getRoles().addAll(user.getRoles().stream().
686 map(Value::getValue).
687 collect(Collectors.toList()));
688
689 return userTO;
690 }
691
692 public UserCR toUserCR(final SCIMUser user) {
693 UserTO userTO = toUserTO(user, true);
694 UserCR userCR = new UserCR();
695 EntityTOUtils.toAnyCR(userTO, userCR);
696 return userCR;
697 }
698
699 protected void setAttribute(final Set<AttrPatch> attrs, final String schema, final SCIMPatchOperation op) {
700 Optional.ofNullable(schema).ifPresent(a -> {
701 Attr.Builder attr = new Attr.Builder(a);
702 if (!CollectionUtils.isEmpty(op.getValue())) {
703 attr.value(op.getValue().get(0).toString());
704 }
705
706 attrs.add(new AttrPatch.Builder(attr.build()).
707 operation(op.getOp() == PatchOp.remove ? PatchOperation.DELETE : PatchOperation.ADD_REPLACE).
708 build());
709 });
710 }
711
712 protected <E extends Enum<?>> void setAttribute(
713 final Set<AttrPatch> attrs,
714 final List<SCIMComplexConf<E>> confs,
715 final SCIMPatchOperation op) {
716
717 confs.stream().
718 filter(conf -> BooleanUtils.toBoolean(JexlUtils.evaluateExpr(
719 filter2JexlExpression(op.getPath().getFilter()),
720 new MapContext(Map.of("type", conf.getType().name()))).toString())).findFirst().
721 ifPresent(conf -> {
722 if (op.getPath().getSub() == null || "display".equals(op.getPath().getSub())) {
723 setAttribute(attrs, conf.getDisplay(), op);
724 }
725 if (op.getPath().getSub() == null || "value".equals(op.getPath().getSub())) {
726 setAttribute(attrs, conf.getValue(), op);
727 }
728 });
729 }
730
731 protected <E extends Enum<?>> void setAttribute(
732 final Set<AttrPatch> attrs,
733 final List<SCIMComplexConf<E>> confs,
734 final List<SCIMComplexValue> values,
735 final PatchOp patchOp) {
736
737 values.stream().
738 filter(value -> value.getType() != null).forEach(value -> confs.stream().
739 filter(conf -> value.getType().equals(conf.getType().name())).findFirst().
740 ifPresent(conf -> attrs.add(new AttrPatch.Builder(
741 new Attr.Builder(conf.getValue()).value(value.getValue()).build()).
742 operation(patchOp == PatchOp.remove ? PatchOperation.DELETE : PatchOperation.ADD_REPLACE).
743 build())));
744 }
745
746 protected void setAttribute(
747 final Set<AttrPatch> attrs,
748 final SCIMUserAddressConf conf,
749 final SCIMPatchOperation op) {
750
751 if (op.getPath().getSub() == null || "formatted".equals(op.getPath().getSub())) {
752 setAttribute(attrs, conf.getFormatted(), op);
753 }
754 if (op.getPath().getSub() == null || "streetAddress".equals(op.getPath().getSub())) {
755 setAttribute(attrs, conf.getStreetAddress(), op);
756 }
757 if (op.getPath().getSub() == null || "locality".equals(op.getPath().getSub())) {
758 setAttribute(attrs, conf.getLocality(), op);
759 }
760 if (op.getPath().getSub() == null || "region".equals(op.getPath().getSub())) {
761 setAttribute(attrs, conf.getRegion(), op);
762 }
763 if (op.getPath().getSub() == null || "postalCode".equals(op.getPath().getSub())) {
764 setAttribute(attrs, conf.getPostalCode(), op);
765 }
766 if (op.getPath().getSub() == null || "country".equals(op.getPath().getSub())) {
767 setAttribute(attrs, conf.getCountry(), op);
768 }
769 }
770
771 public Pair<UserUR, StatusR> toUserUpdate(
772 final UserTO before,
773 final Collection<String> resources,
774 final SCIMPatchOperation op) {
775 StatusR statusR = null;
776
777 if (op.getPath() == null && op.getOp() != PatchOp.remove
778 && !CollectionUtils.isEmpty(op.getValue()) && op.getValue().get(0) instanceof SCIMUser) {
779
780 SCIMUser after = (SCIMUser) op.getValue().get(0);
781
782 if (after.getActive() != null && before.isSuspended() == after.isActive()) {
783 statusR = new StatusR.Builder(
784 before.getKey(),
785 after.isActive() ? StatusRType.REACTIVATE : StatusRType.SUSPEND).
786 resources(resources).
787 build();
788 }
789
790 UserTO updated = toUserTO(after, false);
791 updated.setKey(before.getKey());
792 return Pair.of(AnyOperations.diff(updated, before, true), statusR);
793 }
794
795 UserUR userUR = new UserUR.Builder(before.getKey()).build();
796
797 SCIMConf conf = confManager.get();
798 if (conf == null) {
799 return Pair.of(userUR, statusR);
800 }
801
802 switch (op.getPath().getAttribute()) {
803 case "externalId":
804 setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getExternalId(), op);
805 break;
806
807 case "userName":
808 if (op.getOp() != PatchOp.remove && !CollectionUtils.isEmpty(op.getValue())) {
809 userUR.setUsername(new StringReplacePatchItem.Builder().
810 value(op.getValue().get(0).toString()).build());
811 }
812 break;
813
814 case "password":
815 if (op.getOp() != PatchOp.remove && !CollectionUtils.isEmpty(op.getValue())) {
816 userUR.setPassword(new PasswordPatch.Builder().
817 value(op.getValue().get(0).toString()).build());
818 }
819 break;
820
821 case "active":
822 if (!CollectionUtils.isEmpty(op.getValue())) {
823
824
825 if (op.getValue().get(0) instanceof String) {
826 String a = (String) op.getValue().get(0);
827 op.setValue(List.of(BooleanUtils.toBoolean(a)));
828 }
829
830 statusR = new StatusR.Builder(
831 before.getKey(),
832 (boolean) op.getValue().get(0) ? StatusRType.REACTIVATE : StatusRType.SUSPEND).
833 resources(resources).
834 build();
835 }
836 break;
837
838 case "name":
839 if (conf.getUserConf().getName() != null) {
840 if (op.getPath().getSub() == null || "familyName".equals(op.getPath().getSub())) {
841 setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getName().getFamilyName(), op);
842 }
843 if (op.getPath().getSub() == null || "formatted".equals(op.getPath().getSub())) {
844 setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getName().getFormatted(), op);
845 }
846 if (op.getPath().getSub() == null || "givenName".equals(op.getPath().getSub())) {
847 setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getName().getGivenName(), op);
848 }
849 if (op.getPath().getSub() == null || "honorificPrefix".equals(op.getPath().getSub())) {
850 setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getName().getHonorificPrefix(), op);
851 }
852 if (op.getPath().getSub() == null || "honorificSuffix".equals(op.getPath().getSub())) {
853 setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getName().getHonorificSuffix(), op);
854 }
855 if (op.getPath().getSub() == null || "middleName".equals(op.getPath().getSub())) {
856 setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getName().getMiddleName(), op);
857 }
858 }
859 break;
860
861 case "displayName":
862 setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getDisplayName(), op);
863 break;
864
865 case "nickName":
866 setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getNickName(), op);
867 break;
868
869 case "profileUrl":
870 setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getProfileUrl(), op);
871 break;
872
873 case "title":
874 setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getTitle(), op);
875 break;
876
877 case "userType":
878 setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getUserType(), op);
879 break;
880
881 case "preferredLanguage":
882 setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getPreferredLanguage(), op);
883 break;
884
885 case "locale":
886 setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getLocale(), op);
887 break;
888
889 case "timezone":
890 setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getTimezone(), op);
891 break;
892
893 case "emails":
894 if (!CollectionUtils.isEmpty(op.getValue()) && op.getValue().get(0) instanceof SCIMUser) {
895 setAttribute(
896 userUR.getPlainAttrs(),
897 conf.getUserConf().getEmails(),
898 ((SCIMUser) op.getValue().get(0)).getEmails(),
899 op.getOp());
900 } else if (op.getPath().getFilter() != null) {
901 setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getEmails(), op);
902 }
903 break;
904
905 case "phoneNumbers":
906 if (!CollectionUtils.isEmpty(op.getValue()) && op.getValue().get(0) instanceof SCIMUser) {
907 setAttribute(
908 userUR.getPlainAttrs(),
909 conf.getUserConf().getPhoneNumbers(),
910 ((SCIMUser) op.getValue().get(0)).getPhoneNumbers(),
911 op.getOp());
912 } else if (op.getPath().getFilter() != null) {
913 setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getPhoneNumbers(), op);
914 }
915 break;
916
917 case "ims":
918 if (!CollectionUtils.isEmpty(op.getValue()) && op.getValue().get(0) instanceof SCIMUser) {
919 setAttribute(
920 userUR.getPlainAttrs(),
921 conf.getUserConf().getIms(),
922 ((SCIMUser) op.getValue().get(0)).getIms(),
923 op.getOp());
924 } else if (op.getPath().getFilter() != null) {
925 setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getIms(), op);
926 }
927 break;
928
929 case "photos":
930 if (!CollectionUtils.isEmpty(op.getValue()) && op.getValue().get(0) instanceof SCIMUser) {
931 setAttribute(
932 userUR.getPlainAttrs(),
933 conf.getUserConf().getPhotos(),
934 ((SCIMUser) op.getValue().get(0)).getPhotos(),
935 op.getOp());
936 } else if (op.getPath().getFilter() != null) {
937 setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getPhotos(), op);
938 }
939 break;
940
941 case "addresses":
942 if (!CollectionUtils.isEmpty(op.getValue()) && op.getValue().get(0) instanceof SCIMUser) {
943 SCIMUser after = (SCIMUser) op.getValue().get(0);
944 after.getAddresses().stream().filter(address -> address.getType() != null).
945 forEach(address -> conf.getUserConf().getAddresses().stream().
946 filter(object -> address.getType().equals(object.getType().name())).findFirst().
947 ifPresent(addressConf -> setAttribute(userUR.getPlainAttrs(), addressConf, op)));
948 } else if (op.getPath().getFilter() != null) {
949 conf.getUserConf().getAddresses().stream().
950 filter(addressConf -> BooleanUtils.toBoolean(JexlUtils.evaluateExpr(
951 filter2JexlExpression(op.getPath().getFilter()),
952 new MapContext(Map.of("type", addressConf.getType().name()))).toString())).findFirst().
953 ifPresent(addressConf -> setAttribute(userUR.getPlainAttrs(), addressConf, op));
954 }
955 break;
956
957 case "employeeNumber":
958 setAttribute(userUR.getPlainAttrs(), Optional.ofNullable(conf.getEnterpriseUserConf()).
959 map(SCIMEnterpriseUserConf::getEmployeeNumber).orElse(null), op);
960 break;
961
962 case "costCenter":
963 setAttribute(userUR.getPlainAttrs(), Optional.ofNullable(conf.getEnterpriseUserConf()).
964 map(SCIMEnterpriseUserConf::getCostCenter).orElse(null), op);
965 break;
966
967 case "organization":
968 setAttribute(userUR.getPlainAttrs(), Optional.ofNullable(conf.getEnterpriseUserConf()).
969 map(SCIMEnterpriseUserConf::getOrganization).orElse(null), op);
970 break;
971
972 case "division":
973 setAttribute(userUR.getPlainAttrs(), Optional.ofNullable(conf.getEnterpriseUserConf()).
974 map(SCIMEnterpriseUserConf::getDivision).orElse(null), op);
975 break;
976
977 case "department":
978 setAttribute(userUR.getPlainAttrs(), Optional.ofNullable(conf.getEnterpriseUserConf()).
979 map(SCIMEnterpriseUserConf::getDepartment).orElse(null), op);
980 break;
981
982 case "manager":
983 setAttribute(userUR.getPlainAttrs(), Optional.ofNullable(conf.getEnterpriseUserConf()).
984 map(SCIMEnterpriseUserConf::getManager).map(SCIMManagerConf::getKey).orElse(null), op);
985 break;
986
987 default:
988 }
989
990 return Pair.of(userUR, statusR);
991 }
992
993 public SCIMGroup toSCIMGroup(
994 final GroupTO groupTO,
995 final String location,
996 final List<String> attributes,
997 final List<String> excludedAttributes) {
998
999 SCIMGroup group = new SCIMGroup(
1000 groupTO.getKey(),
1001 new Meta(
1002 Resource.Group,
1003 groupTO.getCreationDate(),
1004 Optional.ofNullable(groupTO.getLastChangeDate()).orElse(groupTO.getCreationDate()),
1005 groupTO.getETagValue(),
1006 location),
1007 output(attributes, excludedAttributes, "displayName", groupTO.getName()));
1008
1009 SCIMConf conf = confManager.get();
1010
1011 Map<String, Attr> attrs = new HashMap<>();
1012 attrs.putAll(EntityTOUtils.buildAttrMap(groupTO.getPlainAttrs()));
1013 attrs.putAll(EntityTOUtils.buildAttrMap(groupTO.getDerAttrs()));
1014 attrs.putAll(EntityTOUtils.buildAttrMap(groupTO.getVirAttrs()));
1015
1016 if (output(attributes, excludedAttributes, "externalId")
1017 && conf.getGroupConf() != null
1018 && conf.getGroupConf().getExternalId() != null
1019 && attrs.containsKey(conf.getGroupConf().getExternalId())) {
1020
1021 group.setExternalId(attrs.get(conf.getGroupConf().getExternalId()).getValues().get(0));
1022 }
1023
1024 MembershipCond membCond = new MembershipCond();
1025 membCond.setGroup(groupTO.getKey());
1026 SearchCond searchCond = SearchCond.getLeaf(membCond);
1027
1028 if (output(attributes, excludedAttributes, "members")) {
1029 int count = userLogic.search(
1030 searchCond, 1, 1, List.of(), SyncopeConstants.ROOT_REALM, true, false).getLeft();
1031
1032 for (int page = 1; page <= (count / AnyDAO.DEFAULT_PAGE_SIZE) + 1; page++) {
1033 List<UserTO> users = userLogic.search(
1034 searchCond,
1035 page,
1036 AnyDAO.DEFAULT_PAGE_SIZE,
1037 List.of(),
1038 SyncopeConstants.ROOT_REALM,
1039 true,
1040 false).
1041 getRight();
1042 users.forEach(userTO -> group.getMembers().add(new Member(
1043 userTO.getKey(),
1044 StringUtils.substringBefore(location, "/Groups") + "/Users/" + userTO.getKey(),
1045 userTO.getUsername())));
1046 }
1047 }
1048
1049 return group;
1050 }
1051
1052 public GroupTO toGroupTO(final SCIMGroup group, final boolean checkSchemas) {
1053 if (checkSchemas && !GROUP_SCHEMAS.equals(group.getSchemas())) {
1054 throw new BadRequestException(ErrorType.invalidValue);
1055 }
1056
1057 GroupTO groupTO = new GroupTO();
1058 groupTO.setRealm(SyncopeConstants.ROOT_REALM);
1059 groupTO.setKey(group.getId());
1060 groupTO.setName(group.getDisplayName());
1061
1062 SCIMConf conf = confManager.get();
1063 if (conf.getGroupConf() != null
1064 && conf.getGroupConf().getExternalId() != null && group.getExternalId() != null) {
1065
1066 groupTO.getPlainAttrs().add(
1067 new Attr.Builder(conf.getGroupConf().getExternalId()).
1068 value(group.getExternalId()).build());
1069 }
1070
1071 return groupTO;
1072 }
1073
1074 public GroupCR toGroupCR(final SCIMGroup group) {
1075 GroupTO groupTO = toGroupTO(group, true);
1076 GroupCR groupCR = new GroupCR();
1077 EntityTOUtils.toAnyCR(groupTO, groupCR);
1078 return groupCR;
1079 }
1080
1081 public GroupUR toGroupUR(final GroupTO before, final SCIMPatchOperation op) {
1082 if (op.getPath() == null) {
1083 throw new UnsupportedOperationException("Empty path not supported for Groups");
1084 }
1085
1086 GroupUR groupUR = new GroupUR.Builder(before.getKey()).build();
1087
1088 if ("displayName".equals(op.getPath().getAttribute())) {
1089 StringReplacePatchItem.Builder name = new StringReplacePatchItem.Builder().
1090 operation(op.getOp() == PatchOp.remove ? PatchOperation.DELETE : PatchOperation.ADD_REPLACE);
1091 if (!CollectionUtils.isEmpty(op.getValue())) {
1092 name.value(op.getValue().get(0).toString());
1093 }
1094 groupUR.setName(name.build());
1095 } else {
1096 SCIMConf conf = confManager.get();
1097 if (conf.getGroupConf() != null) {
1098 setAttribute(groupUR.getPlainAttrs(), conf.getGroupConf().getExternalId(), op);
1099 }
1100 }
1101
1102 return groupUR;
1103 }
1104 }