View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
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       * Translates the given SCIM filter into the equivalent JEXL expression.
92       *
93       * @param filter SCIM filter according to https://www.rfc-editor.org/rfc/rfc7644#section-3.4.2.2
94       * @return translated JEXL expression; see https://commons.apache.org/proper/commons-jexl/reference/syntax.html
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                     // Workaround for Microsoft Entra being not SCIM compliant on PATCH requests
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 }