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         PropagationReporter propagationReporter =
180                 taskExecutor.execute(taskInfos, false, AuthContextUtils.getUsername());
181 
182         ProvisioningResult<RealmTO> result = new ProvisioningResult<>();
183         result.setEntity(binder.getRealmTO(realm, true));
184         result.getPropagationStatuses().addAll(propagationReporter.getStatuses());
185 
186         return result;
187     }
188 
189     @PreAuthorize("hasRole('" + IdRepoEntitlement.REALM_DELETE + "')")
190     public ProvisioningResult<RealmTO> delete(final String fullPath) {
191         Realm realm = Optional.ofNullable(realmDAO.findByFullPath(fullPath)).
192                 orElseThrow(() -> new NotFoundException(fullPath));
193 
194         if (!realmDAO.findChildren(realm).isEmpty()) {
195             throw SyncopeClientException.build(ClientExceptionType.RealmContains);
196         }
197 
198         Set<String> adminRealms = Set.of(realm.getFullPath());
199         AnyCond keyCond = new AnyCond(AttrCond.Type.ISNOTNULL);
200         keyCond.setSchema("key");
201         SearchCond allMatchingCond = SearchCond.getLeaf(keyCond);
202         int users = searchDAO.count(realm, true, adminRealms, allMatchingCond, AnyTypeKind.USER);
203         int groups = searchDAO.count(realm, true, adminRealms, allMatchingCond, AnyTypeKind.GROUP);
204         int anyObjects = searchDAO.count(realm, true, adminRealms, allMatchingCond, AnyTypeKind.ANY_OBJECT);
205         int macroTasks = taskDAO.findByRealm(realm).size();
206         int clientApps = casSPClientAppDAO.findByRealm(realm).size()
207                 + saml2SPClientAppDAO.findByRealm(realm).size()
208                 + oidcRPClientAppDAO.findByRealm(realm).size();
209 
210         if (users + groups + anyObjects + macroTasks + clientApps > 0) {
211             SyncopeClientException realmContains = SyncopeClientException.build(ClientExceptionType.RealmContains);
212             realmContains.getElements().add(users + " user(s)");
213             realmContains.getElements().add(groups + " group(s)");
214             realmContains.getElements().add(anyObjects + " anyObject(s)");
215             realmContains.getElements().add(macroTasks + " command task(s)");
216             realmContains.getElements().add(clientApps + " client app(s)");
217             throw realmContains;
218         }
219 
220         PropagationByResource<String> propByRes = new PropagationByResource<>();
221         propByRes.addAll(ResourceOperation.DELETE, realm.getResourceKeys());
222         List<PropagationTaskInfo> taskInfos = propagationManager.createTasks(realm, propByRes, null);
223         PropagationReporter propagationReporter =
224                 taskExecutor.execute(taskInfos, false, AuthContextUtils.getUsername());
225 
226         ProvisioningResult<RealmTO> result = new ProvisioningResult<>();
227         result.setEntity(binder.getRealmTO(realm, true));
228         result.getPropagationStatuses().addAll(propagationReporter.getStatuses());
229 
230         realmDAO.delete(realm);
231 
232         return result;
233     }
234 
235     @Override
236     protected RealmTO resolveReference(final Method method, final Object... args)
237             throws UnresolvedReferenceException {
238 
239         String fullPath = null;
240 
241         if (ArrayUtils.isNotEmpty(args)) {
242             for (int i = 0; fullPath == null && i < args.length; i++) {
243                 if (args[i] instanceof String) {
244                     fullPath = (String) args[i];
245                 } else if (args[i] instanceof RealmTO) {
246                     fullPath = ((RealmTO) args[i]).getFullPath();
247                 }
248             }
249         }
250 
251         if (fullPath != null) {
252             try {
253                 return binder.getRealmTO(realmDAO.findByFullPath(fullPath), true);
254             } catch (Throwable e) {
255                 LOG.debug("Unresolved reference", e);
256                 throw new UnresolvedReferenceException(e);
257             }
258         }
259 
260         throw new UnresolvedReferenceException();
261     }
262 }