1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.syncope.client.lib;
20
21 import com.fasterxml.jackson.core.type.TypeReference;
22 import com.fasterxml.jackson.databind.json.JsonMapper;
23 import java.io.IOException;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Optional;
28 import java.util.Set;
29 import javax.ws.rs.core.EntityTag;
30 import javax.ws.rs.core.HttpHeaders;
31 import javax.ws.rs.core.MediaType;
32 import javax.ws.rs.core.Response;
33 import org.apache.commons.lang3.tuple.Triple;
34 import org.apache.cxf.configuration.jsse.TLSClientParameters;
35 import org.apache.cxf.jaxrs.client.Client;
36 import org.apache.cxf.jaxrs.client.ClientConfiguration;
37 import org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean;
38 import org.apache.cxf.jaxrs.client.WebClient;
39 import org.apache.cxf.transport.common.gzip.GZIPInInterceptor;
40 import org.apache.cxf.transport.common.gzip.GZIPOutInterceptor;
41 import org.apache.cxf.transport.http.HTTPConduit;
42 import org.apache.cxf.transport.http.asyncclient.AsyncHTTPConduit;
43 import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
44 import org.apache.syncope.client.lib.batch.BatchRequest;
45 import org.apache.syncope.common.lib.SyncopeConstants;
46 import org.apache.syncope.common.lib.search.AnyObjectFiqlSearchConditionBuilder;
47 import org.apache.syncope.common.lib.search.ConnObjectTOFiqlSearchConditionBuilder;
48 import org.apache.syncope.common.lib.search.GroupFiqlSearchConditionBuilder;
49 import org.apache.syncope.common.lib.search.OrderByClauseBuilder;
50 import org.apache.syncope.common.lib.search.UserFiqlSearchConditionBuilder;
51 import org.apache.syncope.common.lib.to.UserTO;
52 import org.apache.syncope.common.rest.api.Preference;
53 import org.apache.syncope.common.rest.api.RESTHeaders;
54 import org.apache.syncope.common.rest.api.service.AccessTokenService;
55 import org.apache.syncope.common.rest.api.service.AnyService;
56 import org.apache.syncope.common.rest.api.service.ExecutableService;
57 import org.apache.syncope.common.rest.api.service.UserSelfService;
58 import org.slf4j.Logger;
59 import org.slf4j.LoggerFactory;
60
61
62
63
64
65 public class SyncopeClient {
66
67 protected static final Logger LOG = LoggerFactory.getLogger(SyncopeClient.class);
68
69 protected static final String HEADER_SPLIT_PROPERTY = "org.apache.cxf.http.header.split";
70
71 protected static final JsonMapper MAPPER = JsonMapper.builder().findAndAddModules().build();
72
73 protected final MediaType mediaType;
74
75 protected final JAXRSClientFactoryBean restClientFactory;
76
77 protected final RestClientExceptionMapper exceptionMapper;
78
79 protected final boolean useCompression;
80
81 protected final HTTPClientPolicy httpClientPolicy;
82
83 protected final TLSClientParameters tlsClientParameters;
84
85 public SyncopeClient(
86 final MediaType mediaType,
87 final JAXRSClientFactoryBean restClientFactory,
88 final RestClientExceptionMapper exceptionMapper,
89 final AuthenticationHandler authHandler,
90 final boolean useCompression,
91 final HTTPClientPolicy httpClientPolicy,
92 final TLSClientParameters tlsClientParameters) {
93
94 this.mediaType = mediaType;
95 this.restClientFactory = restClientFactory;
96 if (this.restClientFactory.getHeaders() == null) {
97 this.restClientFactory.setHeaders(new HashMap<>());
98 }
99 this.exceptionMapper = exceptionMapper;
100 this.useCompression = useCompression;
101 this.httpClientPolicy = httpClientPolicy;
102 this.tlsClientParameters = tlsClientParameters;
103
104 init(authHandler);
105 }
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121 protected void init(final AuthenticationHandler authHandler) {
122 cleanup();
123
124 if (authHandler instanceof AnonymousAuthenticationHandler) {
125 restClientFactory.setUsername(((AnonymousAuthenticationHandler) authHandler).getUsername());
126 restClientFactory.setPassword(((AnonymousAuthenticationHandler) authHandler).getPassword());
127 } else if (authHandler instanceof BasicAuthenticationHandler) {
128 restClientFactory.setUsername(((BasicAuthenticationHandler) authHandler).getUsername());
129 restClientFactory.setPassword(((BasicAuthenticationHandler) authHandler).getPassword());
130
131 String jwt = getService(AccessTokenService.class).login().getHeaderString(RESTHeaders.TOKEN);
132 restClientFactory.getHeaders().put(HttpHeaders.AUTHORIZATION, List.of("Bearer " + jwt));
133
134 restClientFactory.setUsername(null);
135 restClientFactory.setPassword(null);
136 } else if (authHandler instanceof JWTAuthenticationHandler) {
137 restClientFactory.getHeaders().put(
138 HttpHeaders.AUTHORIZATION,
139 List.of("Bearer " + ((JWTAuthenticationHandler) authHandler).getJwt()));
140 }
141 }
142
143 protected void cleanup() {
144 restClientFactory.getHeaders().remove(HttpHeaders.AUTHORIZATION);
145 restClientFactory.getHeaders().remove(RESTHeaders.DELEGATED_BY);
146 restClientFactory.setUsername(null);
147 restClientFactory.setPassword(null);
148 }
149
150
151
152
153
154
155 public String getAddress() {
156 return restClientFactory.getAddress();
157 }
158
159
160
161
162
163
164
165 public SyncopeClient delegatedBy(final String delegating) {
166 if (delegating == null) {
167 restClientFactory.getHeaders().remove(RESTHeaders.DELEGATED_BY);
168 } else {
169 restClientFactory.getHeaders().put(RESTHeaders.DELEGATED_BY, List.of(delegating));
170 }
171 return this;
172 }
173
174
175
176
177 public void refresh() {
178 String jwt = getService(AccessTokenService.class).refresh().getHeaderString(RESTHeaders.TOKEN);
179 restClientFactory.getHeaders().put(HttpHeaders.AUTHORIZATION, List.of("Bearer " + jwt));
180 }
181
182
183
184
185 public void logout() {
186 try {
187 getService(AccessTokenService.class).logout();
188 } catch (Exception e) {
189 LOG.error("While logging out, cleaning up anyway", e);
190 }
191 cleanup();
192 }
193
194
195
196
197
198
199 public void login(final AuthenticationHandler handler) {
200 init(handler);
201 }
202
203
204
205
206
207
208 public static UserFiqlSearchConditionBuilder getUserSearchConditionBuilder() {
209 return new UserFiqlSearchConditionBuilder();
210 }
211
212
213
214
215
216
217 public static GroupFiqlSearchConditionBuilder getGroupSearchConditionBuilder() {
218 return new GroupFiqlSearchConditionBuilder();
219 }
220
221
222
223
224
225
226
227 public static AnyObjectFiqlSearchConditionBuilder getAnyObjectSearchConditionBuilder(final String type) {
228 return new AnyObjectFiqlSearchConditionBuilder(type);
229 }
230
231
232
233
234
235
236 public static ConnObjectTOFiqlSearchConditionBuilder getConnObjectTOFiqlSearchConditionBuilder() {
237 return new ConnObjectTOFiqlSearchConditionBuilder();
238 }
239
240
241
242
243
244
245 public static OrderByClauseBuilder getOrderByClauseBuilder() {
246 return new OrderByClauseBuilder();
247 }
248
249
250
251
252
253
254
255 public String getJWT() {
256 List<String> headerValues = restClientFactory.getHeaders().get(HttpHeaders.AUTHORIZATION);
257 String header = headerValues == null || headerValues.isEmpty()
258 ? null
259 : headerValues.get(0);
260 if (header != null && header.startsWith("Bearer ")) {
261 return header.substring("Bearer ".length());
262
263 }
264 return null;
265 }
266
267
268
269
270
271
272 public String getDomain() {
273 List<String> headerValues = restClientFactory.getHeaders().get(RESTHeaders.DOMAIN);
274 return headerValues == null || headerValues.isEmpty()
275 ? SyncopeConstants.MASTER_DOMAIN
276 : headerValues.get(0);
277 }
278
279
280
281
282
283
284
285
286 public <T> T getService(final Class<T> serviceClass) {
287 T serviceInstance;
288 synchronized (restClientFactory) {
289 restClientFactory.setServiceClass(serviceClass);
290 serviceInstance = restClientFactory.create(serviceClass);
291 }
292
293 Client client = WebClient.client(serviceInstance);
294 client.type(mediaType).accept(mediaType);
295 if (serviceInstance instanceof AnyService || serviceInstance instanceof ExecutableService) {
296 client.accept(RESTHeaders.MULTIPART_MIXED);
297 }
298
299 ClientConfiguration config = WebClient.getConfig(client);
300 config.getRequestContext().put(HEADER_SPLIT_PROPERTY, true);
301 config.getRequestContext().put(AsyncHTTPConduit.USE_ASYNC, Boolean.TRUE);
302 if (useCompression) {
303 config.getInInterceptors().add(new GZIPInInterceptor());
304 config.getOutInterceptors().add(new GZIPOutInterceptor());
305 }
306
307 HTTPConduit httpConduit = (HTTPConduit) config.getConduit();
308 Optional.ofNullable(httpClientPolicy).ifPresent(httpConduit::setClient);
309 Optional.ofNullable(tlsClientParameters).ifPresent(httpConduit::setTlsClientParameters);
310
311 return serviceInstance;
312 }
313
314 public Triple<Map<String, Set<String>>, List<String>, UserTO> self() {
315
316 UserSelfService service = getService(UserSelfService.class);
317 WebClient.getConfig(WebClient.client(service)).getRequestContext().put(HEADER_SPLIT_PROPERTY, false);
318
319 Response response = service.read();
320 if (response.getStatusInfo().getStatusCode() != Response.Status.OK.getStatusCode()) {
321 Exception ex = exceptionMapper.fromResponse(response);
322 if (ex != null) {
323 throw (RuntimeException) ex;
324 }
325 }
326
327 try {
328 return Triple.of(
329 MAPPER.readValue(
330 response.getHeaderString(RESTHeaders.OWNED_ENTITLEMENTS), new TypeReference<>() {
331 }),
332 MAPPER.readValue(
333 response.getHeaderString(RESTHeaders.DELEGATIONS), new TypeReference<>() {
334 }),
335 response.readEntity(UserTO.class));
336 } catch (IOException e) {
337 throw new IllegalStateException(e);
338 }
339 }
340
341
342
343
344
345
346
347
348
349
350 public static <T> T header(final T service, final String key, final Object... values) {
351 WebClient.client(service).header(key, values);
352 return service;
353 }
354
355
356
357
358
359
360
361
362
363 public static <T> T prefer(final T service, final Preference preference) {
364 return header(service, RESTHeaders.PREFER, preference.toString());
365 }
366
367
368
369
370
371
372
373
374
375
376 public static <T> T nullPriorityAsync(final T service, final boolean nullPriorityAsync) {
377 return header(service, RESTHeaders.NULL_PRIORITY_ASYNC, nullPriorityAsync);
378 }
379
380
381
382
383
384
385
386
387
388
389 protected static <T> T match(final T service, final EntityTag etag, final boolean ifNot) {
390 WebClient.client(service).match(etag, ifNot);
391 return service;
392 }
393
394
395
396
397
398
399
400
401
402 public static <T> T ifMatch(final T service, final EntityTag etag) {
403 return match(service, etag, false);
404 }
405
406
407
408
409
410
411
412
413
414 public static <T> T ifNoneMatch(final T service, final EntityTag etag) {
415 return match(service, etag, true);
416 }
417
418
419
420
421
422
423
424
425 public static <T> EntityTag getLatestEntityTag(final T service) {
426 return WebClient.client(service).getResponse().getEntityTag();
427 }
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446 public BatchRequest batch() {
447 return new BatchRequest(
448 mediaType,
449 restClientFactory.getAddress(),
450 restClientFactory.getProviders(),
451 getJWT(),
452 tlsClientParameters);
453 }
454 }