1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.syncope.ext.scimv2.cxf.service;
20
21 import java.util.HashSet;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.Set;
25 import java.util.stream.Collectors;
26 import javax.ws.rs.core.Response;
27 import javax.ws.rs.core.Response.ResponseBuilder;
28 import org.apache.commons.jexl3.MapContext;
29 import org.apache.commons.lang3.ArrayUtils;
30 import org.apache.commons.lang3.BooleanUtils;
31 import org.apache.commons.lang3.StringUtils;
32 import org.apache.syncope.common.lib.AnyOperations;
33 import org.apache.syncope.common.lib.SyncopeConstants;
34 import org.apache.syncope.common.lib.request.MembershipUR;
35 import org.apache.syncope.common.lib.request.UserUR;
36 import org.apache.syncope.common.lib.to.GroupTO;
37 import org.apache.syncope.common.lib.to.ProvisioningResult;
38 import org.apache.syncope.common.lib.to.UserTO;
39 import org.apache.syncope.common.lib.types.PatchOperation;
40 import org.apache.syncope.core.logic.GroupLogic;
41 import org.apache.syncope.core.logic.SCIMDataBinder;
42 import org.apache.syncope.core.logic.UserLogic;
43 import org.apache.syncope.core.logic.scim.SCIMConfManager;
44 import org.apache.syncope.core.persistence.api.dao.AnyDAO;
45 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
46 import org.apache.syncope.core.persistence.api.dao.UserDAO;
47 import org.apache.syncope.core.persistence.api.dao.search.MembershipCond;
48 import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
49 import org.apache.syncope.core.provisioning.api.jexl.JexlUtils;
50 import org.apache.syncope.ext.scimv2.api.BadRequestException;
51 import org.apache.syncope.ext.scimv2.api.data.ListResponse;
52 import org.apache.syncope.ext.scimv2.api.data.Member;
53 import org.apache.syncope.ext.scimv2.api.data.SCIMGroup;
54 import org.apache.syncope.ext.scimv2.api.data.SCIMPatchOp;
55 import org.apache.syncope.ext.scimv2.api.data.SCIMSearchRequest;
56 import org.apache.syncope.ext.scimv2.api.service.SCIMGroupService;
57 import org.apache.syncope.ext.scimv2.api.type.ErrorType;
58 import org.apache.syncope.ext.scimv2.api.type.PatchOp;
59 import org.apache.syncope.ext.scimv2.api.type.Resource;
60 import org.apache.syncope.ext.scimv2.api.type.SortOrder;
61 import org.springframework.util.CollectionUtils;
62
63 public class SCIMGroupServiceImpl extends AbstractSCIMService<SCIMGroup> implements SCIMGroupService {
64
65 public SCIMGroupServiceImpl(
66 final UserDAO userDAO,
67 final GroupDAO groupDAO,
68 final UserLogic userLogic,
69 final GroupLogic groupLogic,
70 final SCIMDataBinder binder,
71 final SCIMConfManager confManager) {
72
73 super(userDAO, groupDAO, userLogic, groupLogic, binder, confManager);
74 }
75
76 private void changeMembership(final String user, final String group, final PatchOp patchOp) {
77 UserUR req = new UserUR.Builder(user).
78 membership(new MembershipUR.Builder(group).operation(patchOp == PatchOp.remove
79 ? PatchOperation.DELETE : PatchOperation.ADD_REPLACE).build()).
80 build();
81 try {
82 userLogic.update(req, false);
83 } catch (Exception e) {
84 LOG.error("While applying {} on membership of {} to {}", patchOp, group, user, e);
85 }
86 }
87
88 @Override
89 public Response create(final SCIMGroup group) {
90
91 ProvisioningResult<GroupTO> result = groupLogic.create(binder.toGroupCR(group), false);
92
93
94 group.getMembers().forEach(member -> changeMembership(
95 member.getValue(), result.getEntity().getKey(), PatchOp.add));
96
97 return createResponse(
98 result.getEntity().getKey(),
99 binder.toSCIMGroup(
100 result.getEntity(),
101 uriInfo.getAbsolutePathBuilder().path(result.getEntity().getKey()).build().toASCIIString(),
102 List.of(),
103 List.of()));
104 }
105
106 @Override
107 public SCIMGroup get(
108 final String id,
109 final String attributes,
110 final String excludedAttributes) {
111
112 return binder.toSCIMGroup(
113 groupLogic.read(id),
114 uriInfo.getAbsolutePathBuilder().build().toASCIIString(),
115 List.of(ArrayUtils.nullToEmpty(StringUtils.split(attributes, ','))),
116 List.of(ArrayUtils.nullToEmpty(StringUtils.split(excludedAttributes, ','))));
117 }
118
119 private Set<String> members(final String group) {
120 Set<String> members = new HashSet<>();
121
122 MembershipCond membCond = new MembershipCond();
123 membCond.setGroup(group);
124 SearchCond searchCond = SearchCond.getLeaf(membCond);
125 int count = userLogic.search(searchCond, 1, 1, List.of(), SyncopeConstants.ROOT_REALM, true, false).getLeft();
126 for (int page = 1; page <= (count / AnyDAO.DEFAULT_PAGE_SIZE) + 1; page++) {
127 members.addAll(userLogic.search(
128 searchCond,
129 page,
130 AnyDAO.DEFAULT_PAGE_SIZE,
131 List.of(),
132 SyncopeConstants.ROOT_REALM,
133 true,
134 false).
135 getRight().stream().map(UserTO::getKey).collect(Collectors.toSet()));
136 }
137
138 return members;
139 }
140
141 @Override
142 public Response update(final String id, final SCIMPatchOp patch) {
143 ResponseBuilder builder = checkETag(Resource.Group, id);
144 if (builder != null) {
145 return builder.build();
146 }
147
148 patch.getOperations().forEach(op -> {
149 if (op.getPath() != null && "members".equals(op.getPath().getAttribute())) {
150 if (CollectionUtils.isEmpty(op.getValue())) {
151 members(id).stream().filter(member -> op.getPath().getFilter() == null
152 ? true
153 : BooleanUtils.toBoolean(JexlUtils.evaluate(
154 SCIMDataBinder.filter2JexlExpression(op.getPath().getFilter()),
155 new MapContext(Map.of("value", member))).toString())).
156 forEach(member -> changeMembership(member, id, op.getOp()));
157 } else {
158 op.getValue().stream().map(Member.class::cast).
159 forEach(member -> changeMembership(member.getValue(), id, op.getOp()));
160 }
161 } else {
162 groupLogic.update(binder.toGroupUR(groupLogic.read(id), op), false);
163 }
164 });
165
166 return updateResponse(
167 id,
168 binder.toSCIMGroup(
169 groupLogic.read(id),
170 uriInfo.getAbsolutePathBuilder().path(id).build().toASCIIString(),
171 List.of(),
172 List.of()));
173 }
174
175 @Override
176 public Response replace(final String id, final SCIMGroup group) {
177 if (!id.equals(group.getId())) {
178 throw new BadRequestException(ErrorType.invalidPath, "Expected " + id + ", found " + group.getId());
179 }
180
181 ResponseBuilder builder = checkETag(Resource.Group, id);
182 if (builder != null) {
183 return builder.build();
184 }
185
186
187 Set<String> beforeMembers = members(id);
188
189
190 ProvisioningResult<GroupTO> result = groupLogic.update(
191 AnyOperations.diff(binder.toGroupTO(group, true), groupLogic.read(id), false), false);
192
193
194 Set<String> afterMembers = new HashSet<>();
195 group.getMembers().forEach(member -> {
196 afterMembers.add(member.getValue());
197
198 if (!beforeMembers.contains(member.getValue())) {
199 changeMembership(member.getValue(), result.getEntity().getKey(), PatchOp.add);
200 }
201 });
202
203 beforeMembers.stream().filter(member -> !afterMembers.contains(member)).forEach(user -> changeMembership(
204 user, result.getEntity().getKey(), PatchOp.remove));
205
206 return updateResponse(
207 result.getEntity().getKey(),
208 binder.toSCIMGroup(
209 result.getEntity(),
210 uriInfo.getAbsolutePathBuilder().path(result.getEntity().getKey()).build().toASCIIString(),
211 List.of(),
212 List.of()));
213 }
214
215 @Override
216 public Response delete(final String id) {
217 ResponseBuilder builder = checkETag(Resource.Group, id);
218 if (builder != null) {
219 return builder.build();
220 }
221
222 anyLogic(Resource.Group).delete(id, false);
223 return Response.noContent().build();
224 }
225
226 @Override
227 public ListResponse<SCIMGroup> search(
228 final String attributes,
229 final String excludedAttributes,
230 final String filter,
231 final String sortBy,
232 final SortOrder sortOrder,
233 final Integer startIndex,
234 final Integer count) {
235
236 SCIMSearchRequest request = new SCIMSearchRequest(filter, sortBy, sortOrder, startIndex, count);
237 if (attributes != null) {
238 request.getAttributes().addAll(
239 List.of(ArrayUtils.nullToEmpty(StringUtils.split(attributes, ','))));
240 }
241 if (excludedAttributes != null) {
242 request.getExcludedAttributes().addAll(
243 List.of(ArrayUtils.nullToEmpty(StringUtils.split(excludedAttributes, ','))));
244 }
245
246 return doSearch(Resource.Group, request);
247 }
248
249 @Override
250 public ListResponse<SCIMGroup> search(final SCIMSearchRequest request) {
251 return doSearch(Resource.Group, request);
252 }
253 }