1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 }