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.ArrayList;
23  import java.util.List;
24  import java.util.Locale;
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.BooleanUtils;
30  import org.apache.commons.lang3.StringUtils;
31  import org.apache.syncope.common.lib.SyncopeClientException;
32  import org.apache.syncope.common.lib.to.ConnIdBundle;
33  import org.apache.syncope.common.lib.to.ConnIdObjectClass;
34  import org.apache.syncope.common.lib.to.ConnInstanceTO;
35  import org.apache.syncope.common.lib.to.PlainSchemaTO;
36  import org.apache.syncope.common.lib.types.AttrSchemaType;
37  import org.apache.syncope.common.lib.types.ClientExceptionType;
38  import org.apache.syncope.common.lib.types.IdMEntitlement;
39  import org.apache.syncope.core.persistence.api.dao.ConnInstanceDAO;
40  import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
41  import org.apache.syncope.core.persistence.api.dao.NotFoundException;
42  import org.apache.syncope.core.persistence.api.entity.ConnInstance;
43  import org.apache.syncope.core.persistence.api.entity.ExternalResource;
44  import org.apache.syncope.core.provisioning.api.ConnIdBundleManager;
45  import org.apache.syncope.core.provisioning.api.ConnectorManager;
46  import org.apache.syncope.core.provisioning.api.data.ConnInstanceDataBinder;
47  import org.apache.syncope.core.provisioning.api.utils.RealmUtils;
48  import org.apache.syncope.core.spring.security.AuthContextUtils;
49  import org.apache.syncope.core.spring.security.DelegatedAdministrationException;
50  import org.identityconnectors.common.l10n.CurrentLocale;
51  import org.identityconnectors.framework.api.ConfigurationProperties;
52  import org.identityconnectors.framework.api.ConnectorKey;
53  import org.identityconnectors.framework.common.objects.AttributeUtil;
54  import org.identityconnectors.framework.common.objects.ObjectClassInfo;
55  import org.springframework.security.access.prepost.PreAuthorize;
56  import org.springframework.transaction.annotation.Transactional;
57  
58  public class ConnectorLogic extends AbstractTransactionalLogic<ConnInstanceTO> {
59  
60      protected final ConnIdBundleManager connIdBundleManager;
61  
62      protected final ConnectorManager connectorManager;
63  
64      protected final ExternalResourceDAO resourceDAO;
65  
66      protected final ConnInstanceDAO connInstanceDAO;
67  
68      protected final ConnInstanceDataBinder binder;
69  
70      public ConnectorLogic(
71              final ConnIdBundleManager connIdBundleManager,
72              final ConnectorManager connectorManager,
73              final ExternalResourceDAO resourceDAO,
74              final ConnInstanceDAO connInstanceDAO,
75              final ConnInstanceDataBinder binder) {
76  
77          this.connIdBundleManager = connIdBundleManager;
78          this.connectorManager = connectorManager;
79          this.resourceDAO = resourceDAO;
80          this.connInstanceDAO = connInstanceDAO;
81          this.binder = binder;
82      }
83  
84      protected void securityChecks(final Set<String> effectiveRealms, final String realm, final String key) {
85          if (!effectiveRealms.stream().anyMatch(realm::startsWith)) {
86              throw new DelegatedAdministrationException(realm, ConnInstance.class.getSimpleName(), key);
87          }
88      }
89  
90      protected ConnInstance doSave(final ConnInstance connInstance) {
91          ConnInstance merged = connInstanceDAO.save(connInstance);
92          merged.getResources().forEach(resource -> {
93              try {
94                  connectorManager.registerConnector(resource);
95              } catch (NotFoundException e) {
96                  LOG.error("While registering connector {} for resource {}", merged, resource, e);
97              }
98          });
99          return merged;
100     }
101 
102     @PreAuthorize("hasRole('" + IdMEntitlement.CONNECTOR_CREATE + "')")
103     public ConnInstanceTO create(final ConnInstanceTO connInstanceTO) {
104         if (connInstanceTO.getAdminRealm() == null) {
105             throw SyncopeClientException.build(ClientExceptionType.InvalidRealm);
106         }
107 
108         Set<String> effectiveRealms = RealmUtils.getEffective(
109                 AuthContextUtils.getAuthorizations().get(IdMEntitlement.CONNECTOR_CREATE),
110                 connInstanceTO.getAdminRealm());
111         securityChecks(effectiveRealms, connInstanceTO.getAdminRealm(), null);
112 
113         return binder.getConnInstanceTO(doSave(binder.getConnInstance(connInstanceTO)));
114     }
115 
116     @PreAuthorize("hasRole('" + IdMEntitlement.CONNECTOR_UPDATE + "')")
117     public ConnInstanceTO update(final ConnInstanceTO connInstanceTO) {
118         if (connInstanceTO.getAdminRealm() == null) {
119             SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidConnInstance);
120             sce.getElements().add("Invalid or null realm specified: " + connInstanceTO.getAdminRealm());
121             throw sce;
122         }
123 
124         Set<String> effectiveRealms = RealmUtils.getEffective(
125                 AuthContextUtils.getAuthorizations().get(IdMEntitlement.CONNECTOR_UPDATE),
126                 connInstanceTO.getAdminRealm());
127         securityChecks(effectiveRealms, connInstanceTO.getAdminRealm(), connInstanceTO.getKey());
128 
129         return binder.getConnInstanceTO(doSave(binder.update(connInstanceTO)));
130     }
131 
132     @PreAuthorize("hasRole('" + IdMEntitlement.CONNECTOR_DELETE + "')")
133     public ConnInstanceTO delete(final String key) {
134         ConnInstance connInstance = Optional.ofNullable(connInstanceDAO.authFind(key)).
135                 orElseThrow(() -> new NotFoundException("Connector '" + key + '\''));
136 
137         Set<String> effectiveRealms = RealmUtils.getEffective(
138                 AuthContextUtils.getAuthorizations().get(IdMEntitlement.CONNECTOR_DELETE),
139                 connInstance.getAdminRealm().getFullPath());
140         securityChecks(effectiveRealms, connInstance.getAdminRealm().getFullPath(), connInstance.getKey());
141 
142         if (!connInstance.getResources().isEmpty()) {
143             SyncopeClientException associatedResources = SyncopeClientException.build(
144                     ClientExceptionType.AssociatedResources);
145             connInstance.getResources().forEach(resource -> associatedResources.getElements().add(resource.getKey()));
146             throw associatedResources;
147         }
148 
149         ConnInstanceTO deleted = binder.getConnInstanceTO(connInstance);
150         connInstanceDAO.delete(key);
151         return deleted;
152     }
153 
154     @PreAuthorize("hasRole('" + IdMEntitlement.CONNECTOR_LIST + "')")
155     @Transactional(readOnly = true)
156     public List<ConnInstanceTO> list(final String lang) {
157         CurrentLocale.set(StringUtils.isBlank(lang) ? Locale.ENGLISH : new Locale(lang));
158 
159         return connInstanceDAO.findAll().stream().map(binder::getConnInstanceTO).collect(Collectors.toList());
160     }
161 
162     @PreAuthorize("hasRole('" + IdMEntitlement.CONNECTOR_READ + "')")
163     @Transactional(readOnly = true)
164     public ConnInstanceTO read(final String key, final String lang) {
165         CurrentLocale.set(StringUtils.isBlank(lang) ? Locale.ENGLISH : new Locale(lang));
166 
167         ConnInstance connInstance = connInstanceDAO.authFind(key);
168         if (connInstance == null) {
169             throw new NotFoundException("Connector '" + key + '\'');
170         }
171 
172         return binder.getConnInstanceTO(connInstance);
173     }
174 
175     @PreAuthorize("hasRole('" + IdMEntitlement.CONNECTOR_READ + "')")
176     @Transactional(readOnly = true)
177     public List<ConnIdBundle> getBundles(final String lang) {
178         if (StringUtils.isBlank(lang)) {
179             CurrentLocale.set(Locale.ENGLISH);
180         } else {
181             CurrentLocale.set(new Locale(lang));
182         }
183 
184         List<ConnIdBundle> connectorBundleTOs = new ArrayList<>();
185         connIdBundleManager.getConnInfoManagers().forEach((uri, cim) -> connectorBundleTOs.addAll(
186                 cim.getConnectorInfos().stream().map(bundle -> {
187                     ConnIdBundle connBundleTO = new ConnIdBundle();
188                     connBundleTO.setDisplayName(bundle.getConnectorDisplayName());
189 
190                     connBundleTO.setLocation(uri.toString());
191 
192                     ConnectorKey key = bundle.getConnectorKey();
193                     connBundleTO.setBundleName(key.getBundleName());
194                     connBundleTO.setConnectorName(key.getConnectorName());
195                     connBundleTO.setVersion(key.getBundleVersion());
196 
197                     ConfigurationProperties properties = connIdBundleManager.getConfigurationProperties(bundle);
198                     connBundleTO.getProperties().addAll(properties.getPropertyNames().stream().
199                             map(propName -> binder.build(properties.getProperty(propName))).
200                             collect(Collectors.toList()));
201 
202                     return connBundleTO;
203                 }).collect(Collectors.toList())));
204 
205         return connectorBundleTOs;
206     }
207 
208     @PreAuthorize("hasRole('" + IdMEntitlement.CONNECTOR_READ + "')")
209     public List<ConnIdObjectClass> buildObjectClassInfo(
210             final ConnInstanceTO connInstanceTO, final boolean includeSpecial) {
211 
212         ConnInstanceTO actual = connInstanceTO;
213         ConnInstance existing = connInstanceDAO.find(connInstanceTO.getKey());
214         if (existing != null) {
215             actual = binder.getConnInstanceTO(existing);
216         }
217 
218         Set<ObjectClassInfo> objectClassInfo = connectorManager.createConnector(
219                 connectorManager.buildConnInstanceOverride(actual, connInstanceTO.getConf(), Optional.empty())).
220                 getObjectClassInfo();
221 
222         return objectClassInfo.stream().map(info -> {
223             ConnIdObjectClass connIdObjectClassTO = new ConnIdObjectClass();
224             connIdObjectClassTO.setType(info.getType());
225             connIdObjectClassTO.setAuxiliary(info.isAuxiliary());
226             connIdObjectClassTO.setContainer(info.isContainer());
227             connIdObjectClassTO.getAttributes().addAll(info.getAttributeInfo().stream().
228                     filter(attrInfo -> includeSpecial || !AttributeUtil.isSpecialName(attrInfo.getName())).
229                     map(attrInfo -> {
230                         PlainSchemaTO schema = new PlainSchemaTO();
231                         schema.setKey(attrInfo.getName());
232                         schema.setMandatoryCondition(BooleanUtils.toStringTrueFalse(attrInfo.isRequired()));
233                         schema.setMultivalue(attrInfo.isMultiValued());
234                         schema.setReadonly(!attrInfo.isUpdateable());
235                         schema.setType(AttrSchemaType.getAttrSchemaTypeByClass(attrInfo.getType()));
236                         return schema;
237                     }).
238                     collect(Collectors.toList()));
239 
240             return connIdObjectClassTO;
241         }).collect(Collectors.toList());
242     }
243 
244     @PreAuthorize("hasRole('" + IdMEntitlement.CONNECTOR_READ + "')")
245     @Transactional(readOnly = true)
246     public void check(final ConnInstanceTO connInstanceTO) {
247         if (connInstanceTO.getAdminRealm() == null) {
248             throw SyncopeClientException.build(ClientExceptionType.InvalidRealm);
249         }
250 
251         connectorManager.createConnector(binder.getConnInstance(connInstanceTO)).test();
252     }
253 
254     @PreAuthorize("hasRole('" + IdMEntitlement.CONNECTOR_READ + "')")
255     @Transactional(readOnly = true)
256     public ConnInstanceTO readByResource(final String resourceName, final String lang) {
257         CurrentLocale.set(StringUtils.isBlank(lang) ? Locale.ENGLISH : new Locale(lang));
258 
259         ExternalResource resource = Optional.ofNullable(resourceDAO.find(resourceName)).
260                 orElseThrow(() -> new NotFoundException("Resource '" + resourceName + '\''));
261         ConnInstanceTO connInstance = binder.getConnInstanceTO(
262                 connectorManager.getConnector(resource).getConnInstance());
263         connInstance.setKey(resource.getConnector().getKey());
264         return connInstance;
265     }
266 
267     @PreAuthorize("hasRole('" + IdMEntitlement.CONNECTOR_RELOAD + "')")
268     @Transactional(readOnly = true)
269     public void reload() {
270         connectorManager.unload();
271         connectorManager.load();
272     }
273 
274     @Override
275     protected ConnInstanceTO resolveReference(final Method method, final Object... args)
276             throws UnresolvedReferenceException {
277 
278         String key = null;
279 
280         if (ArrayUtils.isNotEmpty(args)) {
281             for (int i = 0; key == null && i < args.length; i++) {
282                 if (args[i] instanceof String) {
283                     key = (String) args[i];
284                 } else if (args[i] instanceof ConnInstanceTO) {
285                     key = ((ConnInstanceTO) args[i]).getKey();
286                 }
287             }
288         }
289 
290         if (key != null) {
291             try {
292                 return binder.getConnInstanceTO(connInstanceDAO.find(key));
293             } catch (Throwable ignore) {
294                 LOG.debug("Unresolved reference", ignore);
295                 throw new UnresolvedReferenceException(ignore);
296             }
297         }
298 
299         throw new UnresolvedReferenceException();
300     }
301 }