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(
96                      userLogic.read(id),
97                      userDAO.findAllResourceKeys(id),
98                      op);
99              userLogic.update(update.getLeft(), false);
100             Optional.ofNullable(update.getRight()).ifPresent(statusR -> userLogic.status(statusR, false));
101         });
102 
103         return updateResponse(
104                 id,
105                 binder.toSCIMUser(
106                         userLogic.read(id),
107                         uriInfo.getAbsolutePathBuilder().path(id).build().toASCIIString(),
108                         List.of(),
109                         List.of()));
110     }
111 
112     @Override
113     public Response replace(final String id, final SCIMUser user) {
114         if (!id.equals(user.getId())) {
115             throw new BadRequestException(ErrorType.invalidPath, "Expected " + id + ", found " + user.getId());
116         }
117 
118         ResponseBuilder builder = checkETag(Resource.User, id);
119         if (builder != null) {
120             return builder.build();
121         }
122 
123         UserTO before = userLogic.read(id);
124 
125         ProvisioningResult<UserTO> result = userLogic.update(
126                 AnyOperations.diff(binder.toUserTO(user, true), before, false), false);
127 
128         if (before.isSuspended() == user.isActive()) {
129             StatusR statusR = new StatusR.Builder(
130                     before.getKey(),
131                     user.isActive() ? StatusRType.REACTIVATE : StatusRType.SUSPEND).
132                     build();
133             userLogic.status(statusR, false);
134         }
135 
136         return updateResponse(
137                 result.getEntity().getKey(),
138                 binder.toSCIMUser(
139                         result.getEntity(),
140                         uriInfo.getAbsolutePathBuilder().path(result.getEntity().getKey()).build().toASCIIString(),
141                         List.of(),
142                         List.of()));
143     }
144 
145     @Override
146     public Response delete(final String id) {
147         ResponseBuilder builder = checkETag(Resource.User, id);
148         if (builder != null) {
149             return builder.build();
150         }
151 
152         anyLogic(Resource.User).delete(id, false);
153         return Response.noContent().build();
154     }
155 
156     @Override
157     public ListResponse<SCIMUser> search(
158             final String attributes,
159             final String excludedAttributes,
160             final String filter,
161             final String sortBy,
162             final SortOrder sortOrder,
163             final Integer startIndex,
164             final Integer count) {
165 
166         SCIMSearchRequest request = new SCIMSearchRequest(filter, sortBy, sortOrder, startIndex, count);
167         if (attributes != null) {
168             request.getAttributes().addAll(
169                     List.of(ArrayUtils.nullToEmpty(StringUtils.split(attributes, ','))));
170         }
171         if (excludedAttributes != null) {
172             request.getExcludedAttributes().addAll(
173                     List.of(ArrayUtils.nullToEmpty(StringUtils.split(excludedAttributes, ','))));
174         }
175 
176         return doSearch(Resource.User, request);
177     }
178 
179     @Override
180     public ListResponse<SCIMUser> search(final SCIMSearchRequest request) {
181         return doSearch(Resource.User, request);
182     }
183 }