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.lang.reflect.Method;
22  import java.util.Collection;
23  import java.util.Comparator;
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.ArrayUtils;
30  import org.apache.commons.lang3.tuple.Pair;
31  import org.apache.syncope.common.lib.SyncopeClientException;
32  import org.apache.syncope.common.lib.request.AnyObjectCR;
33  import org.apache.syncope.common.lib.request.AnyObjectUR;
34  import org.apache.syncope.common.lib.request.MembershipUR;
35  import org.apache.syncope.common.lib.request.StringPatchItem;
36  import org.apache.syncope.common.lib.to.AnyObjectTO;
37  import org.apache.syncope.common.lib.to.MembershipTO;
38  import org.apache.syncope.common.lib.to.PropagationStatus;
39  import org.apache.syncope.common.lib.to.ProvisioningResult;
40  import org.apache.syncope.common.lib.types.AnyEntitlement;
41  import org.apache.syncope.common.lib.types.AnyTypeKind;
42  import org.apache.syncope.common.lib.types.ClientExceptionType;
43  import org.apache.syncope.common.lib.types.PatchOperation;
44  import org.apache.syncope.core.logic.api.LogicActions;
45  import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
46  import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
47  import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
48  import org.apache.syncope.core.persistence.api.dao.NotFoundException;
49  import org.apache.syncope.core.persistence.api.dao.RealmDAO;
50  import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
51  import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
52  import org.apache.syncope.core.persistence.api.entity.AnyType;
53  import org.apache.syncope.core.persistence.api.entity.Realm;
54  import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
55  import org.apache.syncope.core.provisioning.api.AnyObjectProvisioningManager;
56  import org.apache.syncope.core.provisioning.api.data.AnyObjectDataBinder;
57  import org.apache.syncope.core.provisioning.api.utils.RealmUtils;
58  import org.apache.syncope.core.provisioning.java.utils.TemplateUtils;
59  import org.apache.syncope.core.spring.security.AuthContextUtils;
60  import org.springframework.transaction.annotation.Transactional;
61  
62  /**
63   * Note that this controller does not extend {@link AbstractTransactionalLogic}, hence does not provide any
64   * Spring's Transactional logic at class level.
65   */
66  public class AnyObjectLogic extends AbstractAnyLogic<AnyObjectTO, AnyObjectCR, AnyObjectUR> {
67  
68      protected final AnyObjectDAO anyObjectDAO;
69  
70      protected final AnySearchDAO searchDAO;
71  
72      protected final AnyObjectDataBinder binder;
73  
74      protected final AnyObjectProvisioningManager provisioningManager;
75  
76      public AnyObjectLogic(
77              final RealmDAO realmDAO,
78              final AnyTypeDAO anyTypeDAO,
79              final TemplateUtils templateUtils,
80              final AnyObjectDAO anyObjectDAO,
81              final AnySearchDAO searchDAO,
82              final AnyObjectDataBinder binder,
83              final AnyObjectProvisioningManager provisioningManager) {
84  
85          super(realmDAO, anyTypeDAO, templateUtils);
86  
87          this.anyObjectDAO = anyObjectDAO;
88          this.searchDAO = searchDAO;
89          this.binder = binder;
90          this.provisioningManager = provisioningManager;
91      }
92  
93      @Transactional(readOnly = true)
94      @Override
95      public AnyObjectTO read(final String key) {
96          return binder.getAnyObjectTO(key);
97      }
98  
99      @Transactional(readOnly = true)
100     public AnyObjectTO read(final String type, final String name) {
101         return Optional.ofNullable(anyObjectDAO.findKey(type, name)).
102                 map(binder::getAnyObjectTO).
103                 orElseThrow(() -> new NotFoundException("AnyObject " + type + " " + name));
104     }
105 
106     @Transactional(readOnly = true)
107     @Override
108     public Pair<Integer, List<AnyObjectTO>> search(
109             final SearchCond searchCond,
110             final int page, final int size, final List<OrderByClause> orderBy,
111             final String realm,
112             final boolean recursive,
113             final boolean details) {
114 
115         if (searchCond.hasAnyTypeCond() == null) {
116             throw new UnsupportedOperationException("Need to specify " + AnyType.class.getSimpleName());
117         }
118 
119         Realm base = Optional.ofNullable(realmDAO.findByFullPath(realm)).
120                 orElseThrow(() -> new NotFoundException("Realm " + realm));
121 
122         Set<String> authRealms = RealmUtils.getEffective(
123                 AuthContextUtils.getAuthorizations().get(AnyEntitlement.SEARCH.getFor(searchCond.hasAnyTypeCond())),
124                 realm);
125 
126         int count = searchDAO.count(base, recursive, authRealms, searchCond, AnyTypeKind.ANY_OBJECT);
127 
128         List<AnyObject> matching = searchDAO.search(
129                 base, recursive, authRealms, searchCond, page, size, orderBy, AnyTypeKind.ANY_OBJECT);
130         List<AnyObjectTO> result = matching.stream().
131                 map(anyObject -> binder.getAnyObjectTO(anyObject, details)).
132                 collect(Collectors.toList());
133 
134         return Pair.of(count, result);
135     }
136 
137     public ProvisioningResult<AnyObjectTO> create(final AnyObjectCR createReq, final boolean nullPriorityAsync) {
138         Pair<AnyObjectCR, List<LogicActions>> before = beforeCreate(createReq);
139 
140         if (before.getLeft().getRealm() == null) {
141             throw SyncopeClientException.build(ClientExceptionType.InvalidRealm);
142         }
143         if (before.getLeft().getType() == null) {
144             throw SyncopeClientException.build(ClientExceptionType.InvalidAnyType);
145         }
146 
147         Set<String> authRealms = RealmUtils.getEffective(
148                 AuthContextUtils.getAuthorizations().get(AnyEntitlement.CREATE.getFor(before.getLeft().getType())),
149                 before.getLeft().getRealm());
150         anyObjectDAO.securityChecks(
151                 authRealms,
152                 null,
153                 before.getLeft().getRealm(),
154                 before.getLeft().getMemberships().stream().filter(Objects::nonNull).
155                         map(MembershipTO::getGroupKey).filter(Objects::nonNull).
156                         collect(Collectors.toSet()));
157 
158         Pair<String, List<PropagationStatus>> created = provisioningManager.create(
159                 before.getLeft(), nullPriorityAsync, AuthContextUtils.getUsername(), REST_CONTEXT);
160 
161         return afterCreate(binder.getAnyObjectTO(created.getKey()), created.getRight(), before.getRight());
162     }
163 
164     protected Set<String> groups(final AnyObjectTO anyObjectTO) {
165         return anyObjectTO.getMemberships().stream().filter(Objects::nonNull).
166                 map(MembershipTO::getGroupKey).filter(Objects::nonNull).
167                 collect(Collectors.toSet());
168     }
169 
170     @Override
171     public ProvisioningResult<AnyObjectTO> update(final AnyObjectUR req, final boolean nullPriorityAsync) {
172         AnyObjectTO anyObjectTO = binder.getAnyObjectTO(req.getKey());
173         Pair<AnyObjectUR, List<LogicActions>> before = beforeUpdate(req, anyObjectTO.getRealm());
174 
175         Set<String> authRealms = RealmUtils.getEffective(
176                 AuthContextUtils.getAuthorizations().get(AnyEntitlement.UPDATE.getFor(anyObjectTO.getType())),
177                 anyObjectTO.getRealm());
178 
179         Set<String> groups = groups(anyObjectTO);
180         groups.removeAll(req.getMemberships().stream().filter(Objects::nonNull).
181                 filter(m -> m.getOperation() == PatchOperation.DELETE).
182                 map(MembershipUR::getGroup).filter(Objects::nonNull).
183                 collect(Collectors.toSet()));
184 
185         anyObjectDAO.securityChecks(
186                 authRealms,
187                 before.getLeft().getKey(),
188                 anyObjectTO.getRealm(),
189                 groups);
190 
191         Pair<AnyObjectUR, List<PropagationStatus>> after = provisioningManager.update(
192                 req, Set.of(), nullPriorityAsync, AuthContextUtils.getUsername(), REST_CONTEXT);
193 
194         ProvisioningResult<AnyObjectTO> result = afterUpdate(
195                 binder.getAnyObjectTO(after.getLeft().getKey()),
196                 after.getRight(),
197                 before.getRight());
198 
199         return result;
200     }
201 
202     @Override
203     public ProvisioningResult<AnyObjectTO> delete(final String key, final boolean nullPriorityAsync) {
204         Pair<AnyObjectTO, List<LogicActions>> before = beforeDelete(binder.getAnyObjectTO(key));
205 
206         Set<String> authRealms = RealmUtils.getEffective(
207                 AuthContextUtils.getAuthorizations().get(AnyEntitlement.DELETE.getFor(before.getLeft().getType())),
208                 before.getLeft().getRealm());
209         anyObjectDAO.securityChecks(
210                 authRealms,
211                 before.getLeft().getKey(),
212                 before.getLeft().getRealm(),
213                 groups(before.getLeft()));
214 
215         List<PropagationStatus> statuses = provisioningManager.delete(
216                 before.getLeft().getKey(), nullPriorityAsync, AuthContextUtils.getUsername(), REST_CONTEXT);
217 
218         AnyObjectTO deletedTO;
219         if (anyObjectDAO.find(before.getLeft().getKey()) == null) {
220             deletedTO = new AnyObjectTO();
221             deletedTO.setKey(before.getLeft().getKey());
222         } else {
223             deletedTO = binder.getAnyObjectTO(before.getLeft().getKey());
224         }
225 
226         return afterDelete(deletedTO, statuses, before.getRight());
227     }
228 
229     protected void updateChecks(final String key) {
230         AnyObject anyObject = anyObjectDAO.authFind(key);
231 
232         Set<String> authRealms = RealmUtils.getEffective(
233                 AuthContextUtils.getAuthorizations().get(AnyEntitlement.UPDATE.getFor(anyObject.getType().getKey())),
234                 anyObject.getRealm().getFullPath());
235         anyObjectDAO.securityChecks(
236                 authRealms,
237                 anyObject.getKey(),
238                 anyObject.getRealm().getFullPath(),
239                 anyObject.getMemberships().stream().
240                         map(m -> m.getRightEnd().getKey()).
241                         collect(Collectors.toSet()));
242     }
243 
244     @Override
245     public AnyObjectTO unlink(final String key, final Collection<String> resources) {
246         updateChecks(key);
247 
248         AnyObjectUR req = new AnyObjectUR.Builder(key).
249                 resources(resources.stream().
250                         map(r -> new StringPatchItem.Builder().operation(PatchOperation.DELETE).value(r).build()).
251                         collect(Collectors.toList())).
252                 build();
253 
254         return binder.getAnyObjectTO(provisioningManager.unlink(req, AuthContextUtils.getUsername(), REST_CONTEXT));
255     }
256 
257     @Override
258     public AnyObjectTO link(final String key, final Collection<String> resources) {
259         updateChecks(key);
260 
261         AnyObjectUR req = new AnyObjectUR.Builder(key).
262                 resources(resources.stream().
263                         map(r -> new StringPatchItem.Builder().operation(PatchOperation.ADD_REPLACE).value(r).build()).
264                         collect(Collectors.toList())).
265                 build();
266 
267         return binder.getAnyObjectTO(provisioningManager.link(req, AuthContextUtils.getUsername(), REST_CONTEXT));
268     }
269 
270     @Override
271     public ProvisioningResult<AnyObjectTO> unassign(
272             final String key, final Collection<String> resources, final boolean nullPriorityAsync) {
273 
274         updateChecks(key);
275 
276         AnyObjectUR req = new AnyObjectUR.Builder(key).
277                 resources(resources.stream().
278                         map(r -> new StringPatchItem.Builder().operation(PatchOperation.DELETE).value(r).build()).
279                         collect(Collectors.toList())).
280                 build();
281 
282         return update(req, nullPriorityAsync);
283     }
284 
285     @Override
286     public ProvisioningResult<AnyObjectTO> assign(
287             final String key,
288             final Collection<String> resources,
289             final boolean changepwd,
290             final String password,
291             final boolean nullPriorityAsync) {
292 
293         updateChecks(key);
294 
295         AnyObjectUR req = new AnyObjectUR.Builder(key).
296                 resources(resources.stream().
297                         map(r -> new StringPatchItem.Builder().operation(PatchOperation.ADD_REPLACE).value(r).build()).
298                         collect(Collectors.toList())).
299                 build();
300         return update(req, nullPriorityAsync);
301     }
302 
303     @Override
304     public ProvisioningResult<AnyObjectTO> deprovision(
305             final String key,
306             final List<String> resources,
307             final boolean nullPriorityAsync) {
308 
309         updateChecks(key);
310 
311         List<PropagationStatus> statuses = provisioningManager.deprovision(
312                 key, resources, nullPriorityAsync, AuthContextUtils.getUsername());
313 
314         ProvisioningResult<AnyObjectTO> result = new ProvisioningResult<>();
315         result.setEntity(binder.getAnyObjectTO(key));
316         result.getPropagationStatuses().addAll(statuses);
317         result.getPropagationStatuses().sort(Comparator.comparing(item -> resources.indexOf(item.getResource())));
318         return result;
319     }
320 
321     @Override
322     public ProvisioningResult<AnyObjectTO> provision(
323             final String key,
324             final List<String> resources,
325             final boolean changePwd,
326             final String password,
327             final boolean nullPriorityAsync) {
328 
329         updateChecks(key);
330 
331         List<PropagationStatus> statuses = provisioningManager.provision(
332                 key, resources, nullPriorityAsync, AuthContextUtils.getUsername());
333 
334         ProvisioningResult<AnyObjectTO> result = new ProvisioningResult<>();
335         result.setEntity(binder.getAnyObjectTO(key));
336         result.getPropagationStatuses().addAll(statuses);
337         result.getPropagationStatuses().sort(Comparator.comparing(item -> resources.indexOf(item.getResource())));
338         return result;
339     }
340 
341     @Override
342     protected AnyObjectTO resolveReference(final Method method, final Object... args)
343             throws UnresolvedReferenceException {
344 
345         String key = null;
346 
347         if (ArrayUtils.isNotEmpty(args)) {
348             for (int i = 0; key == null && i < args.length; i++) {
349                 if (args[i] instanceof String) {
350                     key = (String) args[i];
351                 } else if (args[i] instanceof AnyObjectTO) {
352                     key = ((AnyObjectTO) args[i]).getKey();
353                 } else if (args[i] instanceof AnyObjectUR) {
354                     key = ((AnyObjectUR) args[i]).getKey();
355                 }
356             }
357         }
358 
359         if (key != null) {
360             try {
361                 return binder.getAnyObjectTO(key);
362             } catch (Throwable ignore) {
363                 LOG.debug("Unresolved reference", ignore);
364                 throw new UnresolvedReferenceException(ignore);
365             }
366         }
367 
368         throw new UnresolvedReferenceException();
369     }
370 }