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.time.OffsetDateTime;
22  import java.util.List;
23  import javax.ws.rs.core.Context;
24  import javax.ws.rs.core.EntityTag;
25  import javax.ws.rs.core.Response;
26  import javax.ws.rs.core.Response.ResponseBuilder;
27  import javax.ws.rs.core.UriInfo;
28  import org.apache.commons.lang3.StringUtils;
29  import org.apache.commons.lang3.tuple.Pair;
30  import org.apache.cxf.jaxrs.ext.MessageContext;
31  import org.apache.syncope.common.lib.SyncopeConstants;
32  import org.apache.syncope.common.lib.to.AnyTO;
33  import org.apache.syncope.common.lib.to.GroupTO;
34  import org.apache.syncope.common.lib.to.UserTO;
35  import org.apache.syncope.core.logic.AbstractAnyLogic;
36  import org.apache.syncope.core.logic.GroupLogic;
37  import org.apache.syncope.core.logic.SCIMDataBinder;
38  import org.apache.syncope.core.logic.UserLogic;
39  import org.apache.syncope.core.logic.scim.SCIMConfManager;
40  import org.apache.syncope.core.logic.scim.SearchCondConverter;
41  import org.apache.syncope.core.logic.scim.SearchCondVisitor;
42  import org.apache.syncope.core.persistence.api.dao.AnyDAO;
43  import org.apache.syncope.core.persistence.api.dao.GroupDAO;
44  import org.apache.syncope.core.persistence.api.dao.NotFoundException;
45  import org.apache.syncope.core.persistence.api.dao.UserDAO;
46  import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
47  import org.apache.syncope.ext.scimv2.api.BadRequestException;
48  import org.apache.syncope.ext.scimv2.api.data.ListResponse;
49  import org.apache.syncope.ext.scimv2.api.data.SCIMResource;
50  import org.apache.syncope.ext.scimv2.api.data.SCIMSearchRequest;
51  import org.apache.syncope.ext.scimv2.api.type.ErrorType;
52  import org.apache.syncope.ext.scimv2.api.type.Resource;
53  import org.apache.syncope.ext.scimv2.api.type.SortOrder;
54  import org.slf4j.Logger;
55  import org.slf4j.LoggerFactory;
56  
57  abstract class AbstractSCIMService<R extends SCIMResource> {
58  
59      protected static final Logger LOG = LoggerFactory.getLogger(AbstractSCIMService.class);
60  
61      @Context
62      protected UriInfo uriInfo;
63  
64      @Context
65      protected MessageContext messageContext;
66  
67      protected final UserDAO userDAO;
68  
69      protected final GroupDAO groupDAO;
70  
71      protected final UserLogic userLogic;
72  
73      protected final GroupLogic groupLogic;
74  
75      protected final SCIMDataBinder binder;
76  
77      protected final SCIMConfManager confManager;
78  
79      protected AbstractSCIMService(
80              final UserDAO userDAO,
81              final GroupDAO groupDAO,
82              final UserLogic userLogic,
83              final GroupLogic groupLogic,
84              final SCIMDataBinder binder,
85              final SCIMConfManager confManager) {
86  
87          this.userDAO = userDAO;
88          this.groupDAO = groupDAO;
89          this.userLogic = userLogic;
90          this.groupLogic = groupLogic;
91          this.binder = binder;
92          this.confManager = confManager;
93      }
94  
95      protected AnyDAO<?> anyDAO(final Resource type) {
96          switch (type) {
97              case User:
98                  return userDAO;
99  
100             case Group:
101                 return groupDAO;
102 
103             default:
104                 throw new UnsupportedOperationException();
105         }
106     }
107 
108     protected AbstractAnyLogic<?, ?, ?> anyLogic(final Resource type) {
109         switch (type) {
110             case User:
111                 return userLogic;
112 
113             case Group:
114                 return groupLogic;
115 
116             default:
117                 throw new UnsupportedOperationException();
118         }
119     }
120 
121     protected Response createResponse(final String key, final SCIMResource resource) {
122         return Response.created(uriInfo.getAbsolutePathBuilder().path(key).build()).
123                 entity(resource).
124                 build();
125     }
126 
127     protected Response updateResponse(final String key, final SCIMResource resource) {
128         return Response.ok(uriInfo.getAbsolutePathBuilder().path(key).build()).
129                 entity(resource).
130                 build();
131     }
132 
133     protected ResponseBuilder checkETag(final Resource resource, final String key) {
134         OffsetDateTime lastChange = anyDAO(resource).findLastChange(key);
135         if (lastChange == null) {
136             throw new NotFoundException("Resource" + key + " not found");
137         }
138 
139         return messageContext.getRequest().
140                 evaluatePreconditions(new EntityTag(String.valueOf(lastChange.toInstant().toEpochMilli()), true));
141     }
142 
143     @SuppressWarnings("unchecked")
144     protected ListResponse<R> doSearch(
145             final Resource type,
146             final SCIMSearchRequest request) {
147 
148         if (type == null) {
149             throw new UnsupportedOperationException();
150         }
151 
152         if (request.getCount() > confManager.get().getGeneralConf().getFilterMaxResults()) {
153             throw new BadRequestException(ErrorType.tooMany, "Too many results requested");
154         }
155 
156         SearchCondVisitor visitor = new SearchCondVisitor(type, confManager.get());
157 
158         int startIndex = request.getStartIndex() <= 1
159                 ? 1
160                 : (request.getStartIndex() / AnyDAO.DEFAULT_PAGE_SIZE) + 1;
161 
162         int itemsPerPage = request.getCount() <= 1 ? AnyDAO.DEFAULT_PAGE_SIZE : request.getCount();
163 
164         List<OrderByClause> sort;
165         if (request.getSortBy() == null) {
166             sort = List.of();
167         } else {
168             OrderByClause clause = new OrderByClause();
169             clause.setField(visitor.createAttrCond(request.getSortBy()).getSchema());
170             clause.setDirection(request.getSortOrder() == null || request.getSortOrder() == SortOrder.ascending
171                     ? OrderByClause.Direction.ASC
172                     : OrderByClause.Direction.DESC);
173             sort = List.of(clause);
174         }
175 
176         Pair<Integer, ? extends List<? extends AnyTO>> result = anyLogic(type).search(
177                 StringUtils.isBlank(request.getFilter())
178                 ? null
179                 : SearchCondConverter.convert(visitor, request.getFilter()),
180                 startIndex,
181                 itemsPerPage,
182                 sort,
183                 SyncopeConstants.ROOT_REALM,
184                 true,
185                 false);
186 
187         if (result.getLeft() > confManager.get().getGeneralConf().getFilterMaxResults()) {
188             throw new BadRequestException(ErrorType.tooMany, "Too many results found");
189         }
190 
191         ListResponse<R> response = new ListResponse<>(
192                 result.getLeft(), startIndex == 1 ? 1 : startIndex - 1, itemsPerPage);
193 
194         result.getRight().forEach(anyTO -> {
195             SCIMResource resource = null;
196             if (anyTO instanceof UserTO) {
197                 resource = binder.toSCIMUser(
198                         (UserTO) anyTO,
199                         uriInfo.getAbsolutePathBuilder().path(anyTO.getKey()).build().toASCIIString(),
200                         request.getAttributes(),
201                         request.getExcludedAttributes());
202             } else if (anyTO instanceof GroupTO) {
203                 resource = binder.toSCIMGroup(
204                         (GroupTO) anyTO,
205                         uriInfo.getAbsolutePathBuilder().path(anyTO.getKey()).build().toASCIIString(),
206                         request.getAttributes(),
207                         request.getExcludedAttributes());
208             }
209 
210             if (resource != null) {
211                 response.getResources().add((R) resource);
212             }
213         });
214 
215         return response;
216     }
217 }