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.ext.scimv2.cxf.service;
20  
21  import java.util.List;
22  import java.util.Optional;
23  import javax.ws.rs.core.Response;
24  import javax.ws.rs.core.Response.ResponseBuilder;
25  import org.apache.commons.lang3.ArrayUtils;
26  import org.apache.commons.lang3.StringUtils;
27  import org.apache.commons.lang3.tuple.Pair;
28  import org.apache.syncope.common.lib.AnyOperations;
29  import org.apache.syncope.common.lib.request.StatusR;
30  import org.apache.syncope.common.lib.request.UserUR;
31  import org.apache.syncope.common.lib.to.ProvisioningResult;
32  import org.apache.syncope.common.lib.to.UserTO;
33  import org.apache.syncope.common.lib.types.StatusRType;
34  import org.apache.syncope.core.logic.GroupLogic;
35  import org.apache.syncope.core.logic.SCIMDataBinder;
36  import org.apache.syncope.core.logic.UserLogic;
37  import org.apache.syncope.core.logic.scim.SCIMConfManager;
38  import org.apache.syncope.core.persistence.api.dao.GroupDAO;
39  import org.apache.syncope.core.persistence.api.dao.UserDAO;
40  import org.apache.syncope.ext.scimv2.api.BadRequestException;
41  import org.apache.syncope.ext.scimv2.api.data.ListResponse;
42  import org.apache.syncope.ext.scimv2.api.data.SCIMPatchOp;
43  import org.apache.syncope.ext.scimv2.api.data.SCIMSearchRequest;
44  import org.apache.syncope.ext.scimv2.api.data.SCIMUser;
45  import org.apache.syncope.ext.scimv2.api.service.SCIMUserService;
46  import org.apache.syncope.ext.scimv2.api.type.ErrorType;
47  import org.apache.syncope.ext.scimv2.api.type.Resource;
48  import org.apache.syncope.ext.scimv2.api.type.SortOrder;
49  
50  public class SCIMUserServiceImpl extends AbstractSCIMService<SCIMUser> implements SCIMUserService {
51  
52      public SCIMUserServiceImpl(
53              final UserDAO userDAO,
54              final GroupDAO groupDAO,
55              final UserLogic userLogic,
56              final GroupLogic groupLogic,
57              final SCIMDataBinder binder,
58              final SCIMConfManager confManager) {
59  
60          super(userDAO, groupDAO, userLogic, groupLogic, binder, confManager);
61      }
62  
63      @Override
64      public Response create(final SCIMUser user) {
65          ProvisioningResult<UserTO> result = userLogic.create(binder.toUserCR(user), false);
66          return createResponse(
67                  result.getEntity().getKey(),
68                  binder.toSCIMUser(
69                          result.getEntity(),
70                          uriInfo.getAbsolutePathBuilder().path(result.getEntity().getKey()).build().toASCIIString(),
71                          List.of(),
72                          List.of()));
73      }
74  
75      @Override
76      public SCIMUser get(final String id,
77              final String attributes,
78              final String excludedAttributes) {
79  
80          return binder.toSCIMUser(
81                  userLogic.read(id),
82                  uriInfo.getAbsolutePathBuilder().build().toASCIIString(),
83                  List.of(ArrayUtils.nullToEmpty(StringUtils.split(attributes, ','))),
84                  List.of(ArrayUtils.nullToEmpty(StringUtils.split(excludedAttributes, ','))));
85      }
86  
87      @Override
88      public Response update(final String id, final SCIMPatchOp patch) {
89          ResponseBuilder builder = checkETag(Resource.User, id);
90          if (builder != null) {
91              return builder.build();
92          }
93  
94          patch.getOperations().forEach(op -> {
95              Pair<UserUR, StatusR> update = binder.toUserUpdate(userLogic.read(id), op);
96              userLogic.update(update.getLeft(), false);
97              Optional.ofNullable(update.getRight()).ifPresent(statusR -> userLogic.status(statusR, false));
98          });
99  
100         return updateResponse(
101                 id,
102                 binder.toSCIMUser(
103                         userLogic.read(id),
104                         uriInfo.getAbsolutePathBuilder().path(id).build().toASCIIString(),
105                         List.of(),
106                         List.of()));
107     }
108 
109     @Override
110     public Response replace(final String id, final SCIMUser user) {
111         if (!id.equals(user.getId())) {
112             throw new BadRequestException(ErrorType.invalidPath, "Expected " + id + ", found " + user.getId());
113         }
114 
115         ResponseBuilder builder = checkETag(Resource.User, id);
116         if (builder != null) {
117             return builder.build();
118         }
119 
120         UserTO before = userLogic.read(id);
121 
122         ProvisioningResult<UserTO> result = userLogic.update(
123                 AnyOperations.diff(binder.toUserTO(user, true), before, false), false);
124 
125         if (before.isSuspended() == user.isActive()) {
126             StatusR statusR = new StatusR.Builder(
127                     before.getKey(),
128                     user.isActive() ? StatusRType.REACTIVATE : StatusRType.SUSPEND).
129                     build();
130             userLogic.status(statusR, false);
131         }
132 
133         return updateResponse(
134                 result.getEntity().getKey(),
135                 binder.toSCIMUser(
136                         result.getEntity(),
137                         uriInfo.getAbsolutePathBuilder().path(result.getEntity().getKey()).build().toASCIIString(),
138                         List.of(),
139                         List.of()));
140     }
141 
142     @Override
143     public Response delete(final String id) {
144         ResponseBuilder builder = checkETag(Resource.User, id);
145         if (builder != null) {
146             return builder.build();
147         }
148 
149         anyLogic(Resource.User).delete(id, false);
150         return Response.noContent().build();
151     }
152 
153     @Override
154     public ListResponse<SCIMUser> search(
155             final String attributes,
156             final String excludedAttributes,
157             final String filter,
158             final String sortBy,
159             final SortOrder sortOrder,
160             final Integer startIndex,
161             final Integer count) {
162 
163         SCIMSearchRequest request = new SCIMSearchRequest(filter, sortBy, sortOrder, startIndex, count);
164         if (attributes != null) {
165             request.getAttributes().addAll(
166                     List.of(ArrayUtils.nullToEmpty(StringUtils.split(attributes, ','))));
167         }
168         if (excludedAttributes != null) {
169             request.getExcludedAttributes().addAll(
170                     List.of(ArrayUtils.nullToEmpty(StringUtils.split(excludedAttributes, ','))));
171         }
172 
173         return doSearch(Resource.User, request);
174     }
175 
176     @Override
177     public ListResponse<SCIMUser> search(final SCIMSearchRequest request) {
178         return doSearch(Resource.User, request);
179     }
180 }