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