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.Comparator;
23  import java.util.List;
24  import java.util.Map;
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.StringUtils;
30  import org.apache.commons.lang3.tuple.Pair;
31  import org.apache.syncope.common.lib.SyncopeClientException;
32  import org.apache.syncope.common.lib.to.ProvisioningResult;
33  import org.apache.syncope.common.lib.to.RealmTO;
34  import org.apache.syncope.common.lib.types.AnyTypeKind;
35  import org.apache.syncope.common.lib.types.ClientExceptionType;
36  import org.apache.syncope.common.lib.types.IdRepoEntitlement;
37  import org.apache.syncope.common.lib.types.ResourceOperation;
38  import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
39  import org.apache.syncope.core.persistence.api.dao.CASSPClientAppDAO;
40  import org.apache.syncope.core.persistence.api.dao.DuplicateException;
41  import org.apache.syncope.core.persistence.api.dao.NotFoundException;
42  import org.apache.syncope.core.persistence.api.dao.OIDCRPClientAppDAO;
43  import org.apache.syncope.core.persistence.api.dao.RealmDAO;
44  import org.apache.syncope.core.persistence.api.dao.SAML2SPClientAppDAO;
45  import org.apache.syncope.core.persistence.api.dao.TaskDAO;
46  import org.apache.syncope.core.persistence.api.dao.search.AnyCond;
47  import org.apache.syncope.core.persistence.api.dao.search.AttrCond;
48  import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
49  import org.apache.syncope.core.persistence.api.entity.Realm;
50  import org.apache.syncope.core.provisioning.api.PropagationByResource;
51  import org.apache.syncope.core.provisioning.api.data.RealmDataBinder;
52  import org.apache.syncope.core.provisioning.api.propagation.PropagationManager;
53  import org.apache.syncope.core.provisioning.api.propagation.PropagationReporter;
54  import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor;
55  import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskInfo;
56  import org.apache.syncope.core.spring.security.AuthContextUtils;
57  import org.identityconnectors.framework.common.objects.Attribute;
58  import org.springframework.security.access.prepost.PreAuthorize;
59  import org.springframework.transaction.annotation.Transactional;
60  
61  public class RealmLogic extends AbstractTransactionalLogic<RealmTO> {
62  
63      protected final RealmDAO realmDAO;
64  
65      protected final AnySearchDAO searchDAO;
66  
67      protected final TaskDAO taskDAO;
68  
69      protected final CASSPClientAppDAO casSPClientAppDAO;
70  
71      protected final OIDCRPClientAppDAO oidcRPClientAppDAO;
72  
73      protected final SAML2SPClientAppDAO saml2SPClientAppDAO;
74  
75      protected final RealmDataBinder binder;
76  
77      protected final PropagationManager propagationManager;
78  
79      protected final PropagationTaskExecutor taskExecutor;
80  
81      public RealmLogic(
82              final RealmDAO realmDAO,
83              final AnySearchDAO searchDAO,
84              final TaskDAO taskDAO,
85              final CASSPClientAppDAO casSPClientAppDAO,
86              final OIDCRPClientAppDAO oidcRPClientAppDAO,
87              final SAML2SPClientAppDAO saml2SPClientAppDAO,
88              final RealmDataBinder binder,
89              final PropagationManager propagationManager,
90              final PropagationTaskExecutor taskExecutor) {
91  
92          this.realmDAO = realmDAO;
93          this.searchDAO = searchDAO;
94          this.taskDAO = taskDAO;
95          this.casSPClientAppDAO = casSPClientAppDAO;
96          this.oidcRPClientAppDAO = oidcRPClientAppDAO;
97          this.saml2SPClientAppDAO = saml2SPClientAppDAO;
98          this.binder = binder;
99          this.propagationManager = propagationManager;
100         this.taskExecutor = taskExecutor;
101     }
102 
103     @PreAuthorize("isAuthenticated()")
104     @Transactional(readOnly = true)
105     public Pair<Integer, List<RealmTO>> search(
106             final String keyword,
107             final String base,
108             final int page,
109             final int size) {
110 
111         Realm baseRealm = Optional.ofNullable(base == null ? realmDAO.getRoot() : realmDAO.findByFullPath(base)).
112                 orElseThrow(() -> new NotFoundException(base));
113 
114         int count = realmDAO.countDescendants(baseRealm.getFullPath(), keyword);
115 
116         List<Realm> result = realmDAO.findDescendants(baseRealm.getFullPath(), keyword, page, size);
117 
118         return Pair.of(
119                 count,
120                 result.stream().map(realm -> binder.getRealmTO(
121                 realm,
122                 AuthContextUtils.getAuthorizations().get(IdRepoEntitlement.REALM_SEARCH).stream().
123                         anyMatch(auth -> realm.getFullPath().startsWith(auth)))).
124                         sorted(Comparator.comparing(RealmTO::getFullPath)).
125                         collect(Collectors.toList()));
126     }
127 
128     @PreAuthorize("hasRole('" + IdRepoEntitlement.REALM_CREATE + "')")
129     public ProvisioningResult<RealmTO> create(final String parentPath, final RealmTO realmTO) {
130         Realm parent;
131         if (StringUtils.isBlank(realmTO.getParent())) {
132             parent = Optional.ofNullable(realmDAO.findByFullPath(parentPath)).
133                     orElseThrow(() -> new NotFoundException(parentPath));
134 
135             realmTO.setParent(parent.getFullPath());
136         } else {
137             parent = Optional.ofNullable(realmDAO.find(realmTO.getParent())).
138                     orElseThrow(() -> new NotFoundException(realmTO.getParent()));
139 
140             if (!parent.getFullPath().equals(parentPath)) {
141                 SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidPath);
142                 sce.getElements().add("Mismatching parent realm: " + parentPath + " Vs " + parent.getFullPath());
143                 throw sce;
144             }
145         }
146 
147         String fullPath = StringUtils.appendIfMissing(parent.getFullPath(), "/") + realmTO.getName();
148         if (realmDAO.findByFullPath(fullPath) != null) {
149             throw new DuplicateException(fullPath);
150         }
151 
152         Realm realm = realmDAO.save(binder.create(parent, realmTO));
153         PropagationByResource<String> propByRes = new PropagationByResource<>();
154         propByRes.addAll(ResourceOperation.CREATE, realm.getResourceKeys());
155         List<PropagationTaskInfo> taskInfos = propagationManager.createTasks(realm, propByRes, null);
156         PropagationReporter propagationReporter =
157                 taskExecutor.execute(taskInfos, false, AuthContextUtils.getUsername());
158 
159         ProvisioningResult<RealmTO> result = new ProvisioningResult<>();
160         result.setEntity(binder.getRealmTO(realm, true));
161         result.getPropagationStatuses().addAll(propagationReporter.getStatuses());
162 
163         return result;
164     }
165 
166     @PreAuthorize("hasRole('" + IdRepoEntitlement.REALM_UPDATE + "')")
167     public ProvisioningResult<RealmTO> update(final RealmTO realmTO) {
168         Realm realm = Optional.ofNullable(realmDAO.findByFullPath(realmTO.getFullPath())).
169                 orElseThrow(() -> new NotFoundException(realmTO.getFullPath()));
170 
171         Map<Pair<String, String>, Set<Attribute>> beforeAttrs = propagationManager.prepareAttrs(realm);
172 
173         PropagationByResource<String> propByRes = binder.update(realm, realmTO);
174         realm = realmDAO.save(realm);
175 
176         List<PropagationTaskInfo> taskInfos = propagationManager.setAttributeDeltas(
177                 propagationManager.createTasks(realm, propByRes, null),
178                 beforeAttrs,
179                 null);
180         PropagationReporter propagationReporter =
181                 taskExecutor.execute(taskInfos, false, AuthContextUtils.getUsername());
182 
183         ProvisioningResult<RealmTO> result = new ProvisioningResult<>();
184         result.setEntity(binder.getRealmTO(realm, true));
185         result.getPropagationStatuses().addAll(propagationReporter.getStatuses());
186 
187         return result;
188     }
189 
190     @PreAuthorize("hasRole('" + IdRepoEntitlement.REALM_DELETE + "')")
191     public ProvisioningResult<RealmTO> delete(final String fullPath) {
192         Realm realm = Optional.ofNullable(realmDAO.findByFullPath(fullPath)).
193                 orElseThrow(() -> new NotFoundException(fullPath));
194 
195         if (!realmDAO.findChildren(realm).isEmpty()) {
196             throw SyncopeClientException.build(ClientExceptionType.RealmContains);
197         }
198 
199         Set<String> adminRealms = Set.of(realm.getFullPath());
200         AnyCond keyCond = new AnyCond(AttrCond.Type.ISNOTNULL);
201         keyCond.setSchema("key");
202         SearchCond allMatchingCond = SearchCond.getLeaf(keyCond);
203         int users = searchDAO.count(realm, true, adminRealms, allMatchingCond, AnyTypeKind.USER);
204         int groups = searchDAO.count(realm, true, adminRealms, allMatchingCond, AnyTypeKind.GROUP);
205         int anyObjects = searchDAO.count(realm, true, adminRealms, allMatchingCond, AnyTypeKind.ANY_OBJECT);
206         int macroTasks = taskDAO.findByRealm(realm).size();
207         int clientApps = casSPClientAppDAO.findByRealm(realm).size()
208                 + saml2SPClientAppDAO.findByRealm(realm).size()
209                 + oidcRPClientAppDAO.findByRealm(realm).size();
210 
211         if (users + groups + anyObjects + macroTasks + clientApps > 0) {
212             SyncopeClientException realmContains = SyncopeClientException.build(ClientExceptionType.RealmContains);
213             realmContains.getElements().add(users + " user(s)");
214             realmContains.getElements().add(groups + " group(s)");
215             realmContains.getElements().add(anyObjects + " anyObject(s)");
216             realmContains.getElements().add(macroTasks + " command task(s)");
217             realmContains.getElements().add(clientApps + " client app(s)");
218             throw realmContains;
219         }
220 
221         PropagationByResource<String> propByRes = new PropagationByResource<>();
222         propByRes.addAll(ResourceOperation.DELETE, realm.getResourceKeys());
223         List<PropagationTaskInfo> taskInfos = propagationManager.createTasks(realm, propByRes, null);
224         PropagationReporter propagationReporter =
225                 taskExecutor.execute(taskInfos, false, AuthContextUtils.getUsername());
226 
227         ProvisioningResult<RealmTO> result = new ProvisioningResult<>();
228         result.setEntity(binder.getRealmTO(realm, true));
229         result.getPropagationStatuses().addAll(propagationReporter.getStatuses());
230 
231         realmDAO.delete(realm);
232 
233         return result;
234     }
235 
236     @Override
237     protected RealmTO resolveReference(final Method method, final Object... args)
238             throws UnresolvedReferenceException {
239 
240         String fullPath = null;
241 
242         if (ArrayUtils.isNotEmpty(args)) {
243             for (int i = 0; fullPath == null && i < args.length; i++) {
244                 if (args[i] instanceof String) {
245                     fullPath = (String) args[i];
246                 } else if (args[i] instanceof RealmTO) {
247                     fullPath = ((RealmTO) args[i]).getFullPath();
248                 }
249             }
250         }
251 
252         if (fullPath != null) {
253             try {
254                 return binder.getRealmTO(realmDAO.findByFullPath(fullPath), true);
255             } catch (Throwable e) {
256                 LOG.debug("Unresolved reference", e);
257                 throw new UnresolvedReferenceException(e);
258             }
259         }
260 
261         throw new UnresolvedReferenceException();
262     }
263 }