1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.syncope.core.provisioning.java.pushpull;
20
21 import java.util.ArrayList;
22 import java.util.Collections;
23 import java.util.HashSet;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Optional;
27 import java.util.Set;
28 import java.util.concurrent.ConcurrentHashMap;
29 import org.apache.syncope.common.lib.request.AnyUR;
30 import org.apache.syncope.common.lib.request.MembershipUR;
31 import org.apache.syncope.common.lib.request.UserUR;
32 import org.apache.syncope.common.lib.to.EntityTO;
33 import org.apache.syncope.common.lib.to.GroupTO;
34 import org.apache.syncope.common.lib.to.Provision;
35 import org.apache.syncope.common.lib.to.ProvisioningReport;
36 import org.apache.syncope.common.lib.types.AnyTypeKind;
37 import org.apache.syncope.common.lib.types.PatchOperation;
38 import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
39 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
40 import org.apache.syncope.core.provisioning.api.Connector;
41 import org.apache.syncope.core.provisioning.api.UserProvisioningManager;
42 import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningProfile;
43 import org.apache.syncope.core.provisioning.api.pushpull.PullActions;
44 import org.apache.syncope.core.provisioning.api.rules.PullMatch;
45 import org.apache.syncope.core.spring.implementation.InstanceScope;
46 import org.apache.syncope.core.spring.implementation.SyncopeImplementation;
47 import org.identityconnectors.framework.common.objects.Attribute;
48 import org.identityconnectors.framework.common.objects.ConnectorObject;
49 import org.identityconnectors.framework.common.objects.ObjectClass;
50 import org.identityconnectors.framework.common.objects.OperationOptionsBuilder;
51 import org.identityconnectors.framework.common.objects.SyncDelta;
52 import org.quartz.JobExecutionException;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55 import org.springframework.beans.factory.annotation.Autowired;
56 import org.springframework.transaction.annotation.Propagation;
57 import org.springframework.transaction.annotation.Transactional;
58
59
60
61
62
63
64
65 @SyncopeImplementation(scope = InstanceScope.PER_CONTEXT)
66 public class LDAPMembershipPullActions implements PullActions {
67
68 protected static final Logger LOG = LoggerFactory.getLogger(LDAPMembershipPullActions.class);
69
70 @Autowired
71 protected AnyTypeDAO anyTypeDAO;
72
73 @Autowired
74 protected GroupDAO groupDAO;
75
76 @Autowired
77 protected InboundMatcher inboundMatcher;
78
79 @Autowired
80 protected UserProvisioningManager userProvisioningManager;
81
82 protected final Map<String, Set<String>> membershipsBefore = new ConcurrentHashMap<>();
83
84 protected final Map<String, Set<String>> membershipsAfter = new ConcurrentHashMap<>();
85
86
87
88
89
90
91
92 protected String getGroupMembershipAttrName(final Connector connector) {
93 return connector.getConnInstance().getConf().stream().
94 filter(property -> "groupMemberAttribute".equals(property.getSchema().getName())
95 && !property.getValues().isEmpty()).findFirst().
96 map(groupMembership -> (String) groupMembership.getValues().get(0)).
97 orElse("uniquemember");
98 }
99
100
101
102
103
104
105
106
107
108
109 protected List<Object> getMembAttrValues(final SyncDelta delta, final Connector connector) {
110 String groupMemberName = getGroupMembershipAttrName(connector);
111
112
113 Attribute membAttr = delta.getObject().getAttributeByName(groupMemberName);
114
115 if (membAttr == null) {
116 ConnectorObject remoteObj = connector.getObject(
117 ObjectClass.GROUP,
118 delta.getUid(),
119 false,
120 new OperationOptionsBuilder().setAttributesToGet(groupMemberName).build());
121 if (remoteObj == null) {
122 LOG.debug("Object for '{}' not found", delta.getUid().getUidValue());
123 } else {
124 membAttr = remoteObj.getAttributeByName(groupMemberName);
125 }
126 }
127
128 return membAttr == null || membAttr.getValue() == null
129 ? List.of()
130 : membAttr.getValue();
131 }
132
133
134
135
136
137
138
139
140
141
142
143 @Transactional(readOnly = true)
144 @Override
145 public void beforeUpdate(
146 final ProvisioningProfile<?, ?> profile,
147 final SyncDelta delta,
148 final EntityTO entity,
149 final AnyUR anyUR) throws JobExecutionException {
150
151 if (!(entity instanceof GroupTO)) {
152 PullActions.super.beforeUpdate(profile, delta, entity, anyUR);
153 return;
154 }
155
156 groupDAO.findUMemberships(groupDAO.find(entity.getKey())).forEach(uMembership -> {
157 Set<String> memb = membershipsBefore.computeIfAbsent(
158 uMembership.getLeftEnd().getKey(),
159 k -> Collections.synchronizedSet(new HashSet<>()));
160 memb.add(entity.getKey());
161 });
162 }
163
164
165
166
167
168 @Override
169 public void after(
170 final ProvisioningProfile<?, ?> profile,
171 final SyncDelta delta,
172 final EntityTO entity,
173 final ProvisioningReport result) throws JobExecutionException {
174
175 if (!(entity instanceof GroupTO)) {
176 PullActions.super.after(profile, delta, entity, result);
177 return;
178 }
179
180 Optional<Provision> provision = profile.getTask().getResource().
181 getProvisionByAnyType(AnyTypeKind.USER.name()).filter(p -> p.getMapping() != null);
182 if (provision.isEmpty()) {
183 PullActions.super.after(profile, delta, entity, result);
184 return;
185 }
186
187 getMembAttrValues(delta, profile.getConnector()).forEach(membValue -> {
188 Optional<PullMatch> match = inboundMatcher.match(
189 anyTypeDAO.findUser(),
190 membValue.toString(),
191 profile.getTask().getResource(),
192 profile.getConnector());
193 if (match.isPresent()) {
194 Set<String> memb = membershipsAfter.computeIfAbsent(
195 match.get().getAny().getKey(),
196 k -> Collections.synchronizedSet(new HashSet<>()));
197 memb.add(entity.getKey());
198 } else {
199 LOG.warn("Could not find matching user for {}", membValue);
200 }
201 });
202 }
203
204 @Transactional(propagation = Propagation.REQUIRES_NEW)
205 @Override
206 public void afterAll(final ProvisioningProfile<?, ?> profile) throws JobExecutionException {
207 List<UserUR> updateReqs = new ArrayList<>();
208
209 membershipsAfter.forEach((user, groups) -> {
210 UserUR userUR = new UserUR();
211 userUR.setKey(user);
212 updateReqs.add(userUR);
213
214 groups.stream().forEach(group -> {
215 Set<String> before = membershipsBefore.get(user);
216 if (before == null || !before.contains(group)) {
217 userUR.getMemberships().add(new MembershipUR.Builder(group).
218 operation(PatchOperation.ADD_REPLACE).
219 build());
220 }
221 });
222 });
223
224 membershipsBefore.forEach((user, groups) -> {
225 UserUR userUR = updateReqs.stream().
226 filter(req -> user.equals(req.getKey())).findFirst().
227 orElseGet(() -> {
228 UserUR req = new UserUR.Builder(user).build();
229 updateReqs.add(req);
230 return req;
231 });
232
233 groups.forEach(group -> {
234 Set<String> after = membershipsAfter.get(user);
235 if (after == null || !after.contains(group)) {
236 userUR.getMemberships().add(new MembershipUR.Builder(group).
237 operation(PatchOperation.DELETE).
238 build());
239 }
240 });
241 });
242
243 membershipsAfter.clear();
244 membershipsBefore.clear();
245
246 String context = "PullTask " + profile.getTask().getKey() + " '" + profile.getTask().getName() + "'";
247 updateReqs.stream().filter(req -> !req.isEmpty()).forEach(req -> {
248 LOG.debug("About to update memberships for User {}", req.getKey());
249 userProvisioningManager.update(req, true, profile.getExecutor(), context);
250 });
251 }
252 }