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.HashSet;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Optional;
28 import java.util.Set;
29 import java.util.stream.Collectors;
30 import org.apache.commons.jexl3.MapContext;
31 import org.apache.commons.lang3.BooleanUtils;
32 import org.apache.commons.lang3.StringUtils;
33 import org.apache.commons.lang3.tuple.Pair;
34 import org.apache.syncope.common.lib.AnyOperations;
35 import org.apache.syncope.common.lib.Attr;
36 import org.apache.syncope.common.lib.EntityTOUtils;
37 import org.apache.syncope.common.lib.SyncopeConstants;
38 import org.apache.syncope.common.lib.request.AttrPatch;
39 import org.apache.syncope.common.lib.request.GroupCR;
40 import org.apache.syncope.common.lib.request.GroupUR;
41 import org.apache.syncope.common.lib.request.PasswordPatch;
42 import org.apache.syncope.common.lib.request.StatusR;
43 import org.apache.syncope.common.lib.request.StringReplacePatchItem;
44 import org.apache.syncope.common.lib.request.UserCR;
45 import org.apache.syncope.common.lib.request.UserUR;
46 import org.apache.syncope.common.lib.scim.SCIMComplexConf;
47 import org.apache.syncope.common.lib.scim.SCIMConf;
48 import org.apache.syncope.common.lib.scim.SCIMEnterpriseUserConf;
49 import org.apache.syncope.common.lib.scim.SCIMManagerConf;
50 import org.apache.syncope.common.lib.scim.SCIMUserAddressConf;
51 import org.apache.syncope.common.lib.to.GroupTO;
52 import org.apache.syncope.common.lib.to.MembershipTO;
53 import org.apache.syncope.common.lib.to.UserTO;
54 import org.apache.syncope.common.lib.types.PatchOperation;
55 import org.apache.syncope.common.lib.types.StatusRType;
56 import org.apache.syncope.core.logic.scim.SCIMConfManager;
57 import org.apache.syncope.core.persistence.api.dao.AnyDAO;
58 import org.apache.syncope.core.persistence.api.dao.search.MembershipCond;
59 import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
60 import org.apache.syncope.core.provisioning.api.jexl.JexlUtils;
61 import org.apache.syncope.core.spring.security.AuthDataAccessor;
62 import org.apache.syncope.ext.scimv2.api.BadRequestException;
63 import org.apache.syncope.ext.scimv2.api.data.Group;
64 import org.apache.syncope.ext.scimv2.api.data.Member;
65 import org.apache.syncope.ext.scimv2.api.data.Meta;
66 import org.apache.syncope.ext.scimv2.api.data.SCIMComplexValue;
67 import org.apache.syncope.ext.scimv2.api.data.SCIMEnterpriseInfo;
68 import org.apache.syncope.ext.scimv2.api.data.SCIMExtensionInfo;
69 import org.apache.syncope.ext.scimv2.api.data.SCIMGroup;
70 import org.apache.syncope.ext.scimv2.api.data.SCIMPatchOperation;
71 import org.apache.syncope.ext.scimv2.api.data.SCIMUser;
72 import org.apache.syncope.ext.scimv2.api.data.SCIMUserAddress;
73 import org.apache.syncope.ext.scimv2.api.data.SCIMUserManager;
74 import org.apache.syncope.ext.scimv2.api.data.SCIMUserName;
75 import org.apache.syncope.ext.scimv2.api.data.Value;
76 import org.apache.syncope.ext.scimv2.api.type.ErrorType;
77 import org.apache.syncope.ext.scimv2.api.type.Function;
78 import org.apache.syncope.ext.scimv2.api.type.PatchOp;
79 import org.apache.syncope.ext.scimv2.api.type.Resource;
80 import org.slf4j.Logger;
81 import org.slf4j.LoggerFactory;
82 import org.springframework.util.CollectionUtils;
83
84 public class SCIMDataBinder {
85
86 protected static final Logger LOG = LoggerFactory.getLogger(SCIMDataBinder.class);
87
88 protected static final List<String> GROUP_SCHEMAS = List.of(Resource.Group.schema());
89
90
91
92
93
94
95
96 public static String filter2JexlExpression(final String filter) {
97 String jexlExpression = filter.
98 replace(" co ", " =~ ").
99 replace(" sw ", " =^ ").
100 replace(" ew ", " =$ ");
101
102 boolean endsWithPR = jexlExpression.endsWith(" pr");
103 int pr = endsWithPR ? jexlExpression.indexOf(" pr") : jexlExpression.indexOf(" pr ");
104 while (pr != -1) {
105 String before = jexlExpression.substring(0, pr);
106 int start = before.indexOf(' ') == -1 ? 0 : jexlExpression.substring(0, pr).lastIndexOf(' ', pr) + 1;
107 String literal = jexlExpression.substring(start, pr);
108
109 endsWithPR = jexlExpression.endsWith(" pr");
110 jexlExpression = jexlExpression.replace(
111 literal + " pr" + (endsWithPR ? "" : " "),
112 "not(empty(" + literal + "))" + (endsWithPR ? "" : " "));
113
114 pr = endsWithPR ? jexlExpression.indexOf(" pr") : jexlExpression.indexOf(" pr ");
115 }
116
117 return jexlExpression;
118 }
119
120 protected final SCIMConfManager confManager;
121
122 protected final UserLogic userLogic;
123
124 protected final AuthDataAccessor authDataAccessor;
125
126 public SCIMDataBinder(
127 final SCIMConfManager confManager,
128 final UserLogic userLogic,
129 final AuthDataAccessor authDataAccessor) {
130
131 this.confManager = confManager;
132 this.userLogic = userLogic;
133 this.authDataAccessor = authDataAccessor;
134 }
135
136 protected <E extends Enum<?>> void fill(
137 final Map<String, Attr> attrs,
138 final List<SCIMComplexConf<E>> confs,
139 final List<SCIMComplexValue> values) {
140
141 confs.forEach(conf -> {
142 SCIMComplexValue value = new SCIMComplexValue();
143
144 if (conf.getValue() != null && attrs.containsKey(conf.getValue())) {
145 value.setValue(attrs.get(conf.getValue()).getValues().get(0));
146 }
147 if (conf.getDisplay() != null && attrs.containsKey(conf.getDisplay())) {
148 value.setDisplay(attrs.get(conf.getDisplay()).getValues().get(0));
149 }
150 if (conf.getType() != null) {
151 value.setType(conf.getType().name());
152 }
153
154 value.setPrimary(conf.isPrimary());
155
156 if (!value.isEmpty()) {
157 values.add(value);
158 }
159 });
160 }
161
162 protected boolean output(
163 final List<String> attributes,
164 final List<String> excludedAttributes,
165 final String schema) {
166
167 return (attributes.isEmpty() || attributes.contains(schema))
168 && (excludedAttributes.isEmpty() || !excludedAttributes.contains(schema));
169 }
170
171 protected <T> T output(
172 final List<String> attributes,
173 final List<String> excludedAttributes,
174 final String schema,
175 final T value) {
176
177 return output(attributes, excludedAttributes, schema)
178 ? value
179 : null;
180 }
181
182 public SCIMUser toSCIMUser(
183 final UserTO userTO,
184 final String location,
185 final List<String> attributes,
186 final List<String> excludedAttributes) {
187
188 SCIMConf conf = confManager.get();
189
190 List<String> schemas = new ArrayList<>();
191 schemas.add(Resource.User.schema());
192 if (conf.getEnterpriseUserConf() != null) {
193 schemas.add(Resource.EnterpriseUser.schema());
194 }
195 if (conf.getExtensionUserConf() != null) {
196 schemas.add(Resource.ExtensionUser.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 (conf.getExtensionUserConf() != null) {
448 SCIMExtensionInfo extensionInfo = new SCIMExtensionInfo();
449 conf.getExtensionUserConf().asMap().forEach((scimAttr, syncopeAttr) -> {
450 if (output(attributes, excludedAttributes, scimAttr) && attrs.containsKey(syncopeAttr)) {
451 extensionInfo.getAttributes().put(scimAttr, attrs.get(syncopeAttr).getValues().get(0));
452 }
453 });
454
455 if (!extensionInfo.isEmpty()) {
456 user.setExtensionInfo(extensionInfo);
457 }
458 }
459
460 if (output(attributes, excludedAttributes, "groups")) {
461 userTO.getMemberships().forEach(membership -> user.getGroups().add(new Group(
462 membership.getGroupKey(),
463 StringUtils.substringBefore(location, "/Users") + "/Groups/" + membership.getGroupKey(),
464 membership.getGroupName(),
465 Function.direct)));
466 userTO.getDynMemberships().forEach(membership -> user.getGroups().add(new Group(
467 membership.getGroupKey(),
468 StringUtils.substringBefore(location, "/Users") + "/Groups/" + membership.getGroupKey(),
469 membership.getGroupName(),
470 Function.indirect)));
471 }
472
473 if (output(attributes, excludedAttributes, "entitlements")) {
474 authDataAccessor.getAuthorities(userTO.getUsername(), null).forEach(authority -> user.getEntitlements().
475 add(new Value(authority.getAuthority() + " on Realm(s) " + authority.getRealms())));
476 }
477
478 if (output(attributes, excludedAttributes, "roles")) {
479 userTO.getRoles().forEach(role -> user.getRoles().add(new Value(role)));
480 }
481
482 return user;
483 }
484
485 protected void setAttribute(
486 final UserTO userTO,
487 final String schema,
488 final String value) {
489
490 if (schema == null || value == null) {
491 return;
492 }
493
494 switch (schema) {
495 case "username":
496 userTO.setUsername(value);
497 break;
498
499 default:
500 userTO.getPlainAttrs().add(new Attr.Builder(schema).value(value).build());
501 }
502 }
503
504 protected <E extends Enum<?>> void setAttribute(
505 final Set<Attr> attrs,
506 final List<SCIMComplexConf<E>> confs,
507 final List<SCIMComplexValue> values) {
508
509 values.stream().filter(value -> value.getType() != null).forEach(value -> confs.stream().
510 filter(object -> value.getType().equals(object.getType().name())).findFirst().
511 ifPresent(conf -> attrs.add(
512 new Attr.Builder(conf.getValue()).value(value.getValue()).build())));
513 }
514
515 public UserTO toUserTO(final SCIMUser user, final boolean checkSchemas) {
516 SCIMConf conf = confManager.get();
517
518 Set<String> expectedSchemas = new HashSet<>();
519 expectedSchemas.add(Resource.User.schema());
520 if (conf.getEnterpriseUserConf() != null) {
521 expectedSchemas.add(Resource.EnterpriseUser.schema());
522 }
523 if (conf.getExtensionUserConf() != null) {
524 expectedSchemas.add(Resource.ExtensionUser.schema());
525 }
526 if (checkSchemas
527 && (!user.getSchemas().containsAll(expectedSchemas)
528 || !expectedSchemas.containsAll(user.getSchemas()))) {
529
530 throw new BadRequestException(ErrorType.invalidValue);
531 }
532
533 UserTO userTO = new UserTO();
534 userTO.setRealm(SyncopeConstants.ROOT_REALM);
535 userTO.setKey(user.getId());
536 userTO.setPassword(user.getPassword());
537 userTO.setUsername(user.getUserName());
538
539 if (conf.getUserConf() != null) {
540 setAttribute(
541 userTO,
542 conf.getUserConf().getExternalId(),
543 user.getExternalId());
544
545 if (conf.getUserConf().getName() != null && user.getName() != null) {
546 setAttribute(
547 userTO,
548 conf.getUserConf().getName().getFamilyName(),
549 user.getName().getFamilyName());
550
551 setAttribute(
552 userTO,
553 conf.getUserConf().getName().getFormatted(),
554 user.getName().getFormatted());
555
556 setAttribute(
557 userTO,
558 conf.getUserConf().getName().getGivenName(),
559 user.getName().getGivenName());
560
561 setAttribute(
562 userTO,
563 conf.getUserConf().getName().getHonorificPrefix(),
564 user.getName().getHonorificPrefix());
565
566 setAttribute(
567 userTO,
568 conf.getUserConf().getName().getHonorificSuffix(),
569 user.getName().getHonorificSuffix());
570
571 setAttribute(
572 userTO,
573 conf.getUserConf().getName().getMiddleName(),
574 user.getName().getMiddleName());
575 }
576
577 setAttribute(
578 userTO,
579 conf.getUserConf().getDisplayName(),
580 user.getDisplayName());
581
582 setAttribute(
583 userTO,
584 conf.getUserConf().getNickName(),
585 user.getNickName());
586
587 setAttribute(
588 userTO,
589 conf.getUserConf().getProfileUrl(),
590 user.getProfileUrl());
591
592 setAttribute(
593 userTO,
594 conf.getUserConf().getTitle(),
595 user.getTitle());
596
597 setAttribute(
598 userTO,
599 conf.getUserConf().getUserType(),
600 user.getUserType());
601
602 setAttribute(
603 userTO,
604 conf.getUserConf().getPreferredLanguage(),
605 user.getPreferredLanguage());
606
607 setAttribute(
608 userTO,
609 conf.getUserConf().getLocale(),
610 user.getLocale());
611
612 setAttribute(
613 userTO,
614 conf.getUserConf().getTimezone(),
615 user.getTimezone());
616
617 setAttribute(userTO.getPlainAttrs(), conf.getUserConf().getEmails(), user.getEmails());
618 setAttribute(userTO.getPlainAttrs(), conf.getUserConf().getPhoneNumbers(), user.getPhoneNumbers());
619 setAttribute(userTO.getPlainAttrs(), conf.getUserConf().getIms(), user.getIms());
620 setAttribute(userTO.getPlainAttrs(), conf.getUserConf().getPhotos(), user.getPhotos());
621
622 user.getAddresses().stream().filter(address -> address.getType() != null).
623 forEach(address -> conf.getUserConf().getAddresses().stream().
624 filter(object -> address.getType().equals(object.getType().name())).findFirst().
625 ifPresent(addressConf -> {
626 setAttribute(
627 userTO,
628 addressConf.getFormatted(),
629 address.getFormatted());
630
631 setAttribute(
632 userTO,
633 addressConf.getStreetAddress(),
634 address.getStreetAddress());
635
636 setAttribute(
637 userTO,
638 addressConf.getLocality(),
639 address.getLocality());
640
641 setAttribute(
642 userTO,
643 addressConf.getRegion(),
644 address.getRegion());
645
646 setAttribute(
647 userTO,
648 addressConf.getPostalCode(),
649 address.getPostalCode());
650
651 setAttribute(
652 userTO,
653 addressConf.getCountry(),
654 address.getCountry());
655 }));
656
657 for (int i = 0; i < user.getX509Certificates().size(); i++) {
658 Value certificate = user.getX509Certificates().get(i);
659 if (conf.getUserConf().getX509Certificates().size() > i) {
660 setAttribute(
661 userTO,
662 conf.getUserConf().getX509Certificates().get(i),
663 certificate.getValue());
664 }
665 }
666 }
667
668 if (conf.getEnterpriseUserConf() != null && user.getEnterpriseInfo() != null) {
669 setAttribute(
670 userTO,
671 conf.getEnterpriseUserConf().getEmployeeNumber(),
672 user.getEnterpriseInfo().getEmployeeNumber());
673
674 setAttribute(
675 userTO,
676 conf.getEnterpriseUserConf().getCostCenter(),
677 user.getEnterpriseInfo().getCostCenter());
678
679 setAttribute(
680 userTO,
681 conf.getEnterpriseUserConf().getOrganization(),
682 user.getEnterpriseInfo().getOrganization());
683
684 setAttribute(
685 userTO,
686 conf.getEnterpriseUserConf().getDivision(),
687 user.getEnterpriseInfo().getDivision());
688
689 setAttribute(
690 userTO,
691 conf.getEnterpriseUserConf().getDepartment(),
692 user.getEnterpriseInfo().getDepartment());
693
694 setAttribute(
695 userTO,
696 Optional.ofNullable(conf.getEnterpriseUserConf().getManager()).
697 map(SCIMManagerConf::getKey).orElse(null),
698 Optional.ofNullable(user.getEnterpriseInfo().getManager()).
699 map(SCIMUserManager::getValue).orElse(null));
700 }
701
702 if (conf.getExtensionUserConf() != null && user.getExtensionInfo() != null) {
703 conf.getExtensionUserConf().asMap().forEach((scimAttr, syncopeAttr) -> setAttribute(
704 userTO, syncopeAttr, user.getExtensionInfo().getAttributes().get(scimAttr)));
705 }
706
707 userTO.getMemberships().addAll(user.getGroups().stream().
708 map(group -> new MembershipTO.Builder(group.getValue()).build()).
709 collect(Collectors.toList()));
710
711 userTO.getRoles().addAll(user.getRoles().stream().
712 map(Value::getValue).
713 collect(Collectors.toList()));
714
715 return userTO;
716 }
717
718 public UserCR toUserCR(final SCIMUser user) {
719 UserTO userTO = toUserTO(user, true);
720 UserCR userCR = new UserCR();
721 EntityTOUtils.toAnyCR(userTO, userCR);
722 return userCR;
723 }
724
725 protected void setAttribute(final Set<AttrPatch> attrs, final String schema, final SCIMPatchOperation op) {
726 Optional.ofNullable(schema).ifPresent(a -> {
727 Attr.Builder attr = new Attr.Builder(a);
728 if (!CollectionUtils.isEmpty(op.getValue())) {
729 attr.value(op.getValue().get(0).toString());
730 }
731
732 attrs.add(new AttrPatch.Builder(attr.build()).
733 operation(op.getOp() == PatchOp.remove ? PatchOperation.DELETE : PatchOperation.ADD_REPLACE).
734 build());
735 });
736 }
737
738 protected <E extends Enum<?>> void setAttribute(
739 final Set<AttrPatch> attrs,
740 final List<SCIMComplexConf<E>> confs,
741 final SCIMPatchOperation op) {
742
743 confs.stream().
744 filter(conf -> BooleanUtils.toBoolean(JexlUtils.evaluateExpr(
745 filter2JexlExpression(op.getPath().getFilter()),
746 new MapContext(Map.of("type", conf.getType().name()))).toString())).findFirst().
747 ifPresent(conf -> {
748 if (op.getPath().getSub() == null || "display".equals(op.getPath().getSub())) {
749 setAttribute(attrs, conf.getDisplay(), op);
750 }
751 if (op.getPath().getSub() == null || "value".equals(op.getPath().getSub())) {
752 setAttribute(attrs, conf.getValue(), op);
753 }
754 });
755 }
756
757 protected <E extends Enum<?>> void setAttribute(
758 final Set<AttrPatch> attrs,
759 final List<SCIMComplexConf<E>> confs,
760 final List<SCIMComplexValue> values,
761 final PatchOp patchOp) {
762
763 values.stream().
764 filter(value -> value.getType() != null).forEach(value -> confs.stream().
765 filter(conf -> value.getType().equals(conf.getType().name())).findFirst().
766 ifPresent(conf -> attrs.add(new AttrPatch.Builder(
767 new Attr.Builder(conf.getValue()).value(value.getValue()).build()).
768 operation(patchOp == PatchOp.remove ? PatchOperation.DELETE : PatchOperation.ADD_REPLACE).
769 build())));
770 }
771
772 protected void setAttribute(
773 final Set<AttrPatch> attrs,
774 final SCIMUserAddressConf conf,
775 final SCIMPatchOperation op) {
776
777 if (op.getPath().getSub() == null || "formatted".equals(op.getPath().getSub())) {
778 setAttribute(attrs, conf.getFormatted(), op);
779 }
780 if (op.getPath().getSub() == null || "streetAddress".equals(op.getPath().getSub())) {
781 setAttribute(attrs, conf.getStreetAddress(), op);
782 }
783 if (op.getPath().getSub() == null || "locality".equals(op.getPath().getSub())) {
784 setAttribute(attrs, conf.getLocality(), op);
785 }
786 if (op.getPath().getSub() == null || "region".equals(op.getPath().getSub())) {
787 setAttribute(attrs, conf.getRegion(), op);
788 }
789 if (op.getPath().getSub() == null || "postalCode".equals(op.getPath().getSub())) {
790 setAttribute(attrs, conf.getPostalCode(), op);
791 }
792 if (op.getPath().getSub() == null || "country".equals(op.getPath().getSub())) {
793 setAttribute(attrs, conf.getCountry(), op);
794 }
795 }
796
797 public Pair<UserUR, StatusR> toUserUpdate(
798 final UserTO before,
799 final Collection<String> resources,
800 final SCIMPatchOperation op) {
801 StatusR statusR = null;
802
803 if (op.getPath() == null && op.getOp() != PatchOp.remove
804 && !CollectionUtils.isEmpty(op.getValue()) && op.getValue().get(0) instanceof SCIMUser) {
805
806 SCIMUser after = (SCIMUser) op.getValue().get(0);
807
808 if (after.getActive() != null && before.isSuspended() == after.isActive()) {
809 statusR = new StatusR.Builder(
810 before.getKey(),
811 after.isActive() ? StatusRType.REACTIVATE : StatusRType.SUSPEND).
812 resources(resources).
813 build();
814 }
815
816 UserTO updated = toUserTO(after, false);
817 updated.setKey(before.getKey());
818 return Pair.of(AnyOperations.diff(updated, before, true), statusR);
819 }
820
821 UserUR userUR = new UserUR.Builder(before.getKey()).build();
822
823 SCIMConf conf = confManager.get();
824 if (conf == null) {
825 return Pair.of(userUR, statusR);
826 }
827
828 switch (op.getPath().getAttribute()) {
829 case "externalId":
830 setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getExternalId(), op);
831 break;
832
833 case "userName":
834 if (op.getOp() != PatchOp.remove && !CollectionUtils.isEmpty(op.getValue())) {
835 userUR.setUsername(new StringReplacePatchItem.Builder().
836 value(op.getValue().get(0).toString()).build());
837 }
838 break;
839
840 case "password":
841 if (op.getOp() != PatchOp.remove && !CollectionUtils.isEmpty(op.getValue())) {
842 userUR.setPassword(new PasswordPatch.Builder().
843 value(op.getValue().get(0).toString()).build());
844 }
845 break;
846
847 case "active":
848 if (!CollectionUtils.isEmpty(op.getValue())) {
849
850
851 if (op.getValue().get(0) instanceof String) {
852 String a = (String) op.getValue().get(0);
853 op.setValue(List.of(BooleanUtils.toBoolean(a)));
854 }
855
856 statusR = new StatusR.Builder(
857 before.getKey(),
858 (boolean) op.getValue().get(0) ? StatusRType.REACTIVATE : StatusRType.SUSPEND).
859 resources(resources).
860 build();
861 }
862 break;
863
864 case "name":
865 if (conf.getUserConf().getName() != null) {
866 if (op.getPath().getSub() == null || "familyName".equals(op.getPath().getSub())) {
867 setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getName().getFamilyName(), op);
868 }
869 if (op.getPath().getSub() == null || "formatted".equals(op.getPath().getSub())) {
870 setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getName().getFormatted(), op);
871 }
872 if (op.getPath().getSub() == null || "givenName".equals(op.getPath().getSub())) {
873 setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getName().getGivenName(), op);
874 }
875 if (op.getPath().getSub() == null || "honorificPrefix".equals(op.getPath().getSub())) {
876 setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getName().getHonorificPrefix(), op);
877 }
878 if (op.getPath().getSub() == null || "honorificSuffix".equals(op.getPath().getSub())) {
879 setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getName().getHonorificSuffix(), op);
880 }
881 if (op.getPath().getSub() == null || "middleName".equals(op.getPath().getSub())) {
882 setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getName().getMiddleName(), op);
883 }
884 }
885 break;
886
887 case "displayName":
888 setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getDisplayName(), op);
889 break;
890
891 case "nickName":
892 setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getNickName(), op);
893 break;
894
895 case "profileUrl":
896 setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getProfileUrl(), op);
897 break;
898
899 case "title":
900 setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getTitle(), op);
901 break;
902
903 case "userType":
904 setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getUserType(), op);
905 break;
906
907 case "preferredLanguage":
908 setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getPreferredLanguage(), op);
909 break;
910
911 case "locale":
912 setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getLocale(), op);
913 break;
914
915 case "timezone":
916 setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getTimezone(), op);
917 break;
918
919 case "emails":
920 if (!CollectionUtils.isEmpty(op.getValue()) && op.getValue().get(0) instanceof SCIMUser) {
921 setAttribute(
922 userUR.getPlainAttrs(),
923 conf.getUserConf().getEmails(),
924 ((SCIMUser) op.getValue().get(0)).getEmails(),
925 op.getOp());
926 } else if (op.getPath().getFilter() != null) {
927 setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getEmails(), op);
928 }
929 break;
930
931 case "phoneNumbers":
932 if (!CollectionUtils.isEmpty(op.getValue()) && op.getValue().get(0) instanceof SCIMUser) {
933 setAttribute(
934 userUR.getPlainAttrs(),
935 conf.getUserConf().getPhoneNumbers(),
936 ((SCIMUser) op.getValue().get(0)).getPhoneNumbers(),
937 op.getOp());
938 } else if (op.getPath().getFilter() != null) {
939 setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getPhoneNumbers(), op);
940 }
941 break;
942
943 case "ims":
944 if (!CollectionUtils.isEmpty(op.getValue()) && op.getValue().get(0) instanceof SCIMUser) {
945 setAttribute(
946 userUR.getPlainAttrs(),
947 conf.getUserConf().getIms(),
948 ((SCIMUser) op.getValue().get(0)).getIms(),
949 op.getOp());
950 } else if (op.getPath().getFilter() != null) {
951 setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getIms(), op);
952 }
953 break;
954
955 case "photos":
956 if (!CollectionUtils.isEmpty(op.getValue()) && op.getValue().get(0) instanceof SCIMUser) {
957 setAttribute(
958 userUR.getPlainAttrs(),
959 conf.getUserConf().getPhotos(),
960 ((SCIMUser) op.getValue().get(0)).getPhotos(),
961 op.getOp());
962 } else if (op.getPath().getFilter() != null) {
963 setAttribute(userUR.getPlainAttrs(), conf.getUserConf().getPhotos(), op);
964 }
965 break;
966
967 case "addresses":
968 if (!CollectionUtils.isEmpty(op.getValue()) && op.getValue().get(0) instanceof SCIMUser) {
969 SCIMUser after = (SCIMUser) op.getValue().get(0);
970 after.getAddresses().stream().filter(address -> address.getType() != null).
971 forEach(address -> conf.getUserConf().getAddresses().stream().
972 filter(object -> address.getType().equals(object.getType().name())).findFirst().
973 ifPresent(addressConf -> setAttribute(userUR.getPlainAttrs(), addressConf, op)));
974 } else if (op.getPath().getFilter() != null) {
975 conf.getUserConf().getAddresses().stream().
976 filter(addressConf -> BooleanUtils.toBoolean(JexlUtils.evaluateExpr(
977 filter2JexlExpression(op.getPath().getFilter()),
978 new MapContext(Map.of("type", addressConf.getType().name()))).toString())).findFirst().
979 ifPresent(addressConf -> setAttribute(userUR.getPlainAttrs(), addressConf, op));
980 }
981 break;
982
983 case "employeeNumber":
984 setAttribute(userUR.getPlainAttrs(), Optional.ofNullable(conf.getEnterpriseUserConf()).
985 map(SCIMEnterpriseUserConf::getEmployeeNumber).orElse(null), op);
986 break;
987
988 case "costCenter":
989 setAttribute(userUR.getPlainAttrs(), Optional.ofNullable(conf.getEnterpriseUserConf()).
990 map(SCIMEnterpriseUserConf::getCostCenter).orElse(null), op);
991 break;
992
993 case "organization":
994 setAttribute(userUR.getPlainAttrs(), Optional.ofNullable(conf.getEnterpriseUserConf()).
995 map(SCIMEnterpriseUserConf::getOrganization).orElse(null), op);
996 break;
997
998 case "division":
999 setAttribute(userUR.getPlainAttrs(), Optional.ofNullable(conf.getEnterpriseUserConf()).
1000 map(SCIMEnterpriseUserConf::getDivision).orElse(null), op);
1001 break;
1002
1003 case "department":
1004 setAttribute(userUR.getPlainAttrs(), Optional.ofNullable(conf.getEnterpriseUserConf()).
1005 map(SCIMEnterpriseUserConf::getDepartment).orElse(null), op);
1006 break;
1007
1008 case "manager":
1009 setAttribute(userUR.getPlainAttrs(), Optional.ofNullable(conf.getEnterpriseUserConf()).
1010 map(SCIMEnterpriseUserConf::getManager).map(SCIMManagerConf::getKey).orElse(null), op);
1011 break;
1012
1013 default:
1014 Optional.ofNullable(conf.getExtensionUserConf()).
1015 flatMap(schema -> Optional.ofNullable(schema.asMap().get(op.getPath().getAttribute()))).
1016 ifPresent(schema -> setAttribute(userUR.getPlainAttrs(), schema, op));
1017 }
1018
1019 return Pair.of(userUR, statusR);
1020 }
1021
1022 public SCIMGroup toSCIMGroup(
1023 final GroupTO groupTO,
1024 final String location,
1025 final List<String> attributes,
1026 final List<String> excludedAttributes) {
1027
1028 SCIMGroup group = new SCIMGroup(
1029 groupTO.getKey(),
1030 new Meta(
1031 Resource.Group,
1032 groupTO.getCreationDate(),
1033 Optional.ofNullable(groupTO.getLastChangeDate()).orElse(groupTO.getCreationDate()),
1034 groupTO.getETagValue(),
1035 location),
1036 output(attributes, excludedAttributes, "displayName", groupTO.getName()));
1037
1038 SCIMConf conf = confManager.get();
1039
1040 Map<String, Attr> attrs = new HashMap<>();
1041 attrs.putAll(EntityTOUtils.buildAttrMap(groupTO.getPlainAttrs()));
1042 attrs.putAll(EntityTOUtils.buildAttrMap(groupTO.getDerAttrs()));
1043 attrs.putAll(EntityTOUtils.buildAttrMap(groupTO.getVirAttrs()));
1044
1045 if (output(attributes, excludedAttributes, "externalId")
1046 && conf.getGroupConf() != null
1047 && conf.getGroupConf().getExternalId() != null
1048 && attrs.containsKey(conf.getGroupConf().getExternalId())) {
1049
1050 group.setExternalId(attrs.get(conf.getGroupConf().getExternalId()).getValues().get(0));
1051 }
1052
1053 MembershipCond membCond = new MembershipCond();
1054 membCond.setGroup(groupTO.getKey());
1055 SearchCond searchCond = SearchCond.getLeaf(membCond);
1056
1057 if (output(attributes, excludedAttributes, "members")) {
1058 int count = userLogic.search(
1059 searchCond, 1, 1, List.of(), SyncopeConstants.ROOT_REALM, true, false).getLeft();
1060
1061 for (int page = 1; page <= (count / AnyDAO.DEFAULT_PAGE_SIZE) + 1; page++) {
1062 List<UserTO> users = userLogic.search(
1063 searchCond,
1064 page,
1065 AnyDAO.DEFAULT_PAGE_SIZE,
1066 List.of(),
1067 SyncopeConstants.ROOT_REALM,
1068 true,
1069 false).
1070 getRight();
1071 users.forEach(userTO -> group.getMembers().add(new Member(
1072 userTO.getKey(),
1073 StringUtils.substringBefore(location, "/Groups") + "/Users/" + userTO.getKey(),
1074 userTO.getUsername())));
1075 }
1076 }
1077
1078 return group;
1079 }
1080
1081 public GroupTO toGroupTO(final SCIMGroup group, final boolean checkSchemas) {
1082 if (checkSchemas && !GROUP_SCHEMAS.equals(group.getSchemas())) {
1083 throw new BadRequestException(ErrorType.invalidValue);
1084 }
1085
1086 GroupTO groupTO = new GroupTO();
1087 groupTO.setRealm(SyncopeConstants.ROOT_REALM);
1088 groupTO.setKey(group.getId());
1089 groupTO.setName(group.getDisplayName());
1090
1091 SCIMConf conf = confManager.get();
1092 if (conf.getGroupConf() != null
1093 && conf.getGroupConf().getExternalId() != null && group.getExternalId() != null) {
1094
1095 groupTO.getPlainAttrs().add(
1096 new Attr.Builder(conf.getGroupConf().getExternalId()).
1097 value(group.getExternalId()).build());
1098 }
1099
1100 return groupTO;
1101 }
1102
1103 public GroupCR toGroupCR(final SCIMGroup group) {
1104 GroupTO groupTO = toGroupTO(group, true);
1105 GroupCR groupCR = new GroupCR();
1106 EntityTOUtils.toAnyCR(groupTO, groupCR);
1107 return groupCR;
1108 }
1109
1110 public GroupUR toGroupUR(final GroupTO before, final SCIMPatchOperation op) {
1111 if (op.getPath() == null) {
1112 throw new UnsupportedOperationException("Empty path not supported for Groups");
1113 }
1114
1115 GroupUR groupUR = new GroupUR.Builder(before.getKey()).build();
1116
1117 if ("displayName".equals(op.getPath().getAttribute())) {
1118 StringReplacePatchItem.Builder name = new StringReplacePatchItem.Builder().
1119 operation(op.getOp() == PatchOp.remove ? PatchOperation.DELETE : PatchOperation.ADD_REPLACE);
1120 if (!CollectionUtils.isEmpty(op.getValue())) {
1121 name.value(op.getValue().get(0).toString());
1122 }
1123 groupUR.setName(name.build());
1124 } else {
1125 SCIMConf conf = confManager.get();
1126 if (conf.getGroupConf() != null) {
1127 setAttribute(groupUR.getPlainAttrs(), conf.getGroupConf().getExternalId(), op);
1128 }
1129 }
1130
1131 return groupUR;
1132 }
1133 }