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.provisioning.java.utils;
20  
21  import java.util.ArrayList;
22  import java.util.Arrays;
23  import java.util.Base64;
24  import java.util.List;
25  import java.util.Objects;
26  import java.util.Optional;
27  import java.util.Set;
28  import java.util.stream.Collectors;
29  import org.apache.commons.lang3.StringUtils;
30  import org.apache.syncope.common.lib.AnyOperations;
31  import org.apache.syncope.common.lib.Attr;
32  import org.apache.syncope.common.lib.EntityTOUtils;
33  import org.apache.syncope.common.lib.request.AnyCR;
34  import org.apache.syncope.common.lib.request.AnyUR;
35  import org.apache.syncope.common.lib.request.UserCR;
36  import org.apache.syncope.common.lib.to.AnyObjectTO;
37  import org.apache.syncope.common.lib.to.AnyTO;
38  import org.apache.syncope.common.lib.to.ConnObject;
39  import org.apache.syncope.common.lib.to.GroupTO;
40  import org.apache.syncope.common.lib.to.OrgUnit;
41  import org.apache.syncope.common.lib.to.Provision;
42  import org.apache.syncope.common.lib.to.RealmTO;
43  import org.apache.syncope.common.lib.to.UserTO;
44  import org.apache.syncope.common.lib.types.AnyTypeKind;
45  import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
46  import org.apache.syncope.core.persistence.api.dao.RealmDAO;
47  import org.apache.syncope.core.persistence.api.dao.UserDAO;
48  import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
49  import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
50  import org.apache.syncope.core.persistence.api.entity.task.PullTask;
51  import org.apache.syncope.core.persistence.api.entity.user.User;
52  import org.apache.syncope.core.provisioning.api.MappingManager;
53  import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
54  import org.apache.syncope.core.spring.security.Encryptor;
55  import org.apache.syncope.core.spring.security.PasswordGenerator;
56  import org.identityconnectors.common.security.GuardedByteArray;
57  import org.identityconnectors.common.security.GuardedString;
58  import org.identityconnectors.common.security.SecurityUtil;
59  import org.identityconnectors.framework.common.objects.Attribute;
60  import org.identityconnectors.framework.common.objects.ConnectorObject;
61  import org.identityconnectors.framework.common.objects.SyncToken;
62  import org.slf4j.Logger;
63  import org.slf4j.LoggerFactory;
64  import org.springframework.transaction.annotation.Transactional;
65  import org.springframework.util.CollectionUtils;
66  
67  public class ConnObjectUtils {
68  
69      protected static final Logger LOG = LoggerFactory.getLogger(ConnObjectUtils.class);
70  
71      protected static final Encryptor ENCRYPTOR = Encryptor.getInstance();
72  
73      public static SyncToken toSyncToken(final String syncToken) {
74          return Optional.ofNullable(syncToken).map(st -> POJOHelper.deserialize(st, SyncToken.class)).orElse(null);
75      }
76  
77      public static String toString(final SyncToken syncToken) {
78          return Optional.ofNullable(syncToken).map(POJOHelper::serialize).orElse(null);
79      }
80  
81      /**
82       * Extract password value from passed value (if instance of GuardedString or GuardedByteArray).
83       *
84       * @param pwd received from the underlying connector
85       * @return password value
86       */
87      public static String getPassword(final Object pwd) {
88          StringBuilder result = new StringBuilder();
89  
90          if (pwd instanceof GuardedString) {
91              result.append(SecurityUtil.decrypt((GuardedString) pwd));
92          } else if (pwd instanceof GuardedByteArray) {
93              result.append(Arrays.toString(SecurityUtil.decrypt((GuardedByteArray) pwd)));
94          } else if (pwd instanceof String) {
95              result.append((String) pwd);
96          } else {
97              result.append(pwd.toString());
98          }
99  
100         return result.toString();
101     }
102 
103     /**
104      * Builds {@link ConnObject} out of a collection of {@link Attribute} instances.
105      *
106      * @param fiql FIQL expression to uniquely identify the given Connector Object
107      * @param attrs attributes
108      * @return transfer object
109      */
110     public static ConnObject getConnObjectTO(final String fiql, final Set<Attribute> attrs) {
111         ConnObject connObjectTO = new ConnObject();
112         connObjectTO.setFiql(fiql);
113 
114         if (!CollectionUtils.isEmpty(attrs)) {
115             connObjectTO.getAttrs().addAll(attrs.stream().map(attr -> {
116                 Attr attrTO = new Attr();
117                 attrTO.setSchema(attr.getName());
118 
119                 if (!CollectionUtils.isEmpty(attr.getValue())) {
120                     attr.getValue().stream().filter(Objects::nonNull).forEach(value -> {
121                         if (value instanceof GuardedString || value instanceof GuardedByteArray) {
122                             attrTO.getValues().add(getPassword(value));
123                         } else if (value instanceof byte[]) {
124                             attrTO.getValues().add(Base64.getEncoder().encodeToString((byte[]) value));
125                         } else {
126                             attrTO.getValues().add(value.toString());
127                         }
128                     });
129                 }
130 
131                 return attrTO;
132             }).collect(Collectors.toList()));
133         }
134 
135         return connObjectTO;
136     }
137 
138     protected final TemplateUtils templateUtils;
139 
140     protected final RealmDAO realmDAO;
141 
142     protected final UserDAO userDAO;
143 
144     protected final ExternalResourceDAO resourceDAO;
145 
146     protected final PasswordGenerator passwordGenerator;
147 
148     protected final MappingManager mappingManager;
149 
150     protected final AnyUtilsFactory anyUtilsFactory;
151 
152     public ConnObjectUtils(
153             final TemplateUtils templateUtils,
154             final RealmDAO realmDAO,
155             final UserDAO userDAO,
156             final ExternalResourceDAO resourceDAO,
157             final PasswordGenerator passwordGenerator,
158             final MappingManager mappingManager,
159             final AnyUtilsFactory anyUtilsFactory) {
160 
161         this.templateUtils = templateUtils;
162         this.realmDAO = realmDAO;
163         this.userDAO = userDAO;
164         this.resourceDAO = resourceDAO;
165         this.passwordGenerator = passwordGenerator;
166         this.mappingManager = mappingManager;
167         this.anyUtilsFactory = anyUtilsFactory;
168     }
169 
170     /**
171      * Build a UserCR / GroupCR / AnyObjectCR out of connector object attributes and schema mapping.
172      *
173      * @param obj connector object
174      * @param pullTask pull task
175      * @param anyTypeKind any type kind
176      * @param provision provision information
177      * @param generatePassword whether password value shall be generated, in case not found from
178      * connector object
179      * @param <C> create request type
180      * @return create request
181      */
182     @Transactional(readOnly = true)
183     public <C extends AnyCR> C getAnyCR(
184             final ConnectorObject obj,
185             final PullTask pullTask,
186             final AnyTypeKind anyTypeKind,
187             final Provision provision,
188             final boolean generatePassword) {
189 
190         AnyTO anyTO = getAnyTOFromConnObject(obj, pullTask, anyTypeKind, provision);
191         C anyCR = anyUtilsFactory.getInstance(anyTypeKind).newAnyCR();
192         EntityTOUtils.toAnyCR(anyTO, anyCR);
193 
194         // (for users) if password was not set above, generate if possible
195         if (anyCR instanceof UserCR
196                 && StringUtils.isBlank(((UserCR) anyCR).getPassword())
197                 && generatePassword) {
198 
199             UserCR userCR = (UserCR) anyCR;
200             List<PasswordPolicy> passwordPolicies = new ArrayList<>();
201 
202             // add resource policies
203             userCR.getResources().stream().
204                     map(resourceDAO::find).
205                     filter(r -> r != null && r.getPasswordPolicy() != null).
206                     forEach(r -> passwordPolicies.add(r.getPasswordPolicy()));
207 
208             // add realm policies
209             Optional.ofNullable(realmDAO.findByFullPath(userCR.getRealm())).
210                     ifPresent(realm -> realmDAO.findAncestors(realm).stream().
211                     filter(ancestor -> ancestor.getPasswordPolicy() != null
212                     && !passwordPolicies.contains(ancestor.getPasswordPolicy())).
213                     forEach(ancestor -> passwordPolicies.add(ancestor.getPasswordPolicy())));
214 
215             userCR.setPassword(passwordGenerator.generate(passwordPolicies));
216         }
217 
218         return anyCR;
219     }
220 
221     public RealmTO getRealmTO(final ConnectorObject obj, final OrgUnit orgUnit) {
222         RealmTO realmTO = new RealmTO();
223 
224         MappingUtils.getPullItems(orgUnit.getItems().stream()).
225                 forEach(item -> mappingManager.setIntValues(
226                 item, obj.getAttributeByName(item.getExtAttrName()), realmTO));
227 
228         return realmTO;
229     }
230 
231     /**
232      * Build {@link AnyUR} out of connector object attributes and schema mapping.
233      *
234      * @param key any object to be updated
235      * @param obj connector object
236      * @param original any object to get diff from
237      * @param pullTask pull task
238      * @param anyTypeKind any type kind
239      * @param provision provision information
240      * @param <U> any object
241      * @return modifications for the any object to be updated
242      */
243     @SuppressWarnings("unchecked")
244     @Transactional(readOnly = true)
245     public <U extends AnyUR> U getAnyUR(
246             final String key,
247             final ConnectorObject obj,
248             final AnyTO original,
249             final PullTask pullTask,
250             final AnyTypeKind anyTypeKind,
251             final Provision provision) {
252 
253         AnyTO updated = getAnyTOFromConnObject(obj, pullTask, anyTypeKind, provision);
254         updated.setKey(key);
255 
256         U anyUR;
257         switch (provision.getAnyType()) {
258             case "USER":
259                 UserTO originalUser = (UserTO) original;
260                 UserTO updatedUser = (UserTO) updated;
261 
262                 if (StringUtils.isBlank(updatedUser.getUsername())) {
263                     updatedUser.setUsername(originalUser.getUsername());
264                 }
265 
266                 // update password if and only if password is really changed
267                 User user = userDAO.authFind(key);
268                 if (StringUtils.isBlank(updatedUser.getPassword())
269                         || ENCRYPTOR.verify(updatedUser.getPassword(),
270                                 user.getCipherAlgorithm(), user.getPassword())) {
271 
272                     updatedUser.setPassword(null);
273                 }
274 
275                 updatedUser.setSecurityQuestion(originalUser.getSecurityQuestion());
276 
277                 if (!mappingManager.hasMustChangePassword(provision)) {
278                     updatedUser.setMustChangePassword(originalUser.isMustChangePassword());
279                 }
280 
281                 anyUR = (U) AnyOperations.diff(updatedUser, originalUser, true);
282                 break;
283 
284             case "GROUP":
285                 GroupTO originalGroup = (GroupTO) original;
286                 GroupTO updatedGroup = (GroupTO) updated;
287 
288                 if (StringUtils.isBlank(updatedGroup.getName())) {
289                     updatedGroup.setName(originalGroup.getName());
290                 }
291                 updatedGroup.setUserOwner(originalGroup.getUserOwner());
292                 updatedGroup.setGroupOwner(originalGroup.getGroupOwner());
293                 updatedGroup.setUDynMembershipCond(originalGroup.getUDynMembershipCond());
294                 updatedGroup.getADynMembershipConds().putAll(originalGroup.getADynMembershipConds());
295                 updatedGroup.getTypeExtensions().addAll(originalGroup.getTypeExtensions());
296 
297                 anyUR = (U) AnyOperations.diff(updatedGroup, originalGroup, true);
298                 break;
299 
300             default:
301                 AnyObjectTO originalAnyObject = (AnyObjectTO) original;
302                 AnyObjectTO updatedAnyObject = (AnyObjectTO) updated;
303 
304                 if (StringUtils.isBlank(updatedAnyObject.getName())) {
305                     updatedAnyObject.setName(originalAnyObject.getName());
306                 }
307 
308                 anyUR = (U) AnyOperations.diff(updatedAnyObject, originalAnyObject, true);
309         }
310 
311         if (anyUR != null) {
312             // ensure not to include incidental realm changes in the patch
313             anyUR.setRealm(null);
314 
315             // SYNCOPE-1343, remove null or empty values from the patch plain attributes
316             AnyOperations.cleanEmptyAttrs(updated, anyUR);
317         }
318         return anyUR;
319     }
320 
321     protected <T extends AnyTO> T getAnyTOFromConnObject(
322             final ConnectorObject obj,
323             final PullTask pullTask,
324             final AnyTypeKind anyTypeKind,
325             final Provision provision) {
326 
327         T anyTO = anyUtilsFactory.getInstance(anyTypeKind).newAnyTO();
328         anyTO.setType(provision.getAnyType());
329         anyTO.getAuxClasses().addAll(provision.getAuxClasses());
330 
331         // 1. fill with data from connector object
332         anyTO.setRealm(pullTask.getDestinationRealm().getFullPath());
333         MappingUtils.getPullItems(provision.getMapping().getItems().stream()).forEach(
334                 item -> mappingManager.setIntValues(item, obj.getAttributeByName(item.getExtAttrName()), anyTO));
335 
336         // 2. add data from defined template (if any)
337         templateUtils.apply(anyTO, pullTask.getTemplate(provision.getAnyType()));
338 
339         return anyTO;
340     }
341 }