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.client.lib;
20  
21  import com.fasterxml.jackson.jakarta.rs.json.JacksonJsonProvider;
22  import java.util.HashMap;
23  import java.util.List;
24  import java.util.Optional;
25  import org.apache.commons.lang3.StringUtils;
26  import org.apache.cxf.configuration.jsse.TLSClientParameters;
27  import org.apache.cxf.ext.logging.LoggingFeature;
28  import org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean;
29  import org.apache.cxf.transports.http.configuration.ConnectionType;
30  import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
31  import org.apache.syncope.common.lib.jackson.SyncopeJsonMapper;
32  import org.apache.syncope.common.rest.api.DateParamConverterProvider;
33  import org.apache.syncope.common.rest.api.RESTHeaders;
34  
35  /**
36   * Factory bean for creating instances of {@link SyncopeClient}.
37   * Supports Spring-bean configuration and override via subclassing (see protected methods).
38   */
39  public class SyncopeClientFactoryBean {
40  
41      private JacksonJsonProvider jsonProvider;
42  
43      private RestClientExceptionMapper exceptionMapper;
44  
45      private String address;
46  
47      private String domain;
48  
49      private boolean useCompression;
50  
51      private HTTPClientPolicy httpClientPolicy;
52  
53      private TLSClientParameters tlsClientParameters;
54  
55      private JAXRSClientFactoryBean restClientFactoryBean;
56  
57      protected static JacksonJsonProvider defaultJsonProvider() {
58          return new JacksonJsonProvider(new SyncopeJsonMapper());
59      }
60  
61      protected static RestClientExceptionMapper defaultExceptionMapper() {
62          return new RestClientExceptionMapper();
63      }
64  
65      protected static HTTPClientPolicy defaultHTTPClientPolicy() {
66          HTTPClientPolicy policy = new HTTPClientPolicy();
67          policy.setConnection(ConnectionType.CLOSE);
68          return policy;
69      }
70  
71      protected JAXRSClientFactoryBean defaultRestClientFactoryBean() {
72          JAXRSClientFactoryBean defaultRestClientFactoryBean = new JAXRSClientFactoryBean();
73          defaultRestClientFactoryBean.setHeaders(new HashMap<>());
74  
75          if (StringUtils.isBlank(address)) {
76              throw new IllegalArgumentException("Property 'address' is missing");
77          }
78          defaultRestClientFactoryBean.setAddress(address);
79  
80          if (StringUtils.isNotBlank(domain)) {
81              defaultRestClientFactoryBean.getHeaders().put(RESTHeaders.DOMAIN, List.of(domain));
82          }
83  
84          defaultRestClientFactoryBean.setThreadSafe(true);
85          defaultRestClientFactoryBean.setInheritHeaders(true);
86  
87          defaultRestClientFactoryBean.setFeatures(List.of(new LoggingFeature()));
88  
89          defaultRestClientFactoryBean.setProviders(List.of(
90                  new DateParamConverterProvider(),
91                  getJsonProvider(),
92                  getExceptionMapper()));
93  
94          return defaultRestClientFactoryBean;
95      }
96  
97      public JacksonJsonProvider getJsonProvider() {
98          return Optional.ofNullable(jsonProvider).orElseGet(SyncopeClientFactoryBean::defaultJsonProvider);
99      }
100 
101     public void setJsonProvider(final JacksonJsonProvider jsonProvider) {
102         this.jsonProvider = jsonProvider;
103     }
104 
105     public RestClientExceptionMapper getExceptionMapper() {
106         return Optional.ofNullable(exceptionMapper).orElseGet(SyncopeClientFactoryBean::defaultExceptionMapper);
107     }
108 
109     public SyncopeClientFactoryBean setExceptionMapper(final RestClientExceptionMapper exceptionMapper) {
110         this.exceptionMapper = exceptionMapper;
111         return this;
112     }
113 
114     public String getAddress() {
115         return address;
116     }
117 
118     public SyncopeClientFactoryBean setAddress(final String address) {
119         this.address = address;
120         return this;
121     }
122 
123     public String getDomain() {
124         return domain;
125     }
126 
127     public SyncopeClientFactoryBean setDomain(final String domain) {
128         this.domain = domain;
129         return this;
130     }
131 
132     /**
133      * Sets the given service instance for transparent gzip {@code Content-Encoding} handling.
134      *
135      * @param useCompression whether transparent gzip {@code Content-Encoding} handling is to be enabled
136      * @return the current instance
137      */
138     public SyncopeClientFactoryBean setUseCompression(final boolean useCompression) {
139         this.useCompression = useCompression;
140         return this;
141     }
142 
143     public boolean isUseCompression() {
144         return useCompression;
145     }
146 
147     public SyncopeClientFactoryBean setHttpClientPolicy(final HTTPClientPolicy httpClientPolicy) {
148         this.httpClientPolicy = httpClientPolicy;
149         return this;
150     }
151 
152     public HTTPClientPolicy getHttpClientPolicy() {
153         return Optional.ofNullable(httpClientPolicy).orElseGet(SyncopeClientFactoryBean::defaultHTTPClientPolicy);
154     }
155 
156     /**
157      * Sets the client TLS configuration.
158      *
159      * @param tlsClientParameters client TLS configuration
160      * @return the current instance
161      */
162     public SyncopeClientFactoryBean setTlsClientParameters(final TLSClientParameters tlsClientParameters) {
163         this.tlsClientParameters = tlsClientParameters;
164         return this;
165     }
166 
167     public TLSClientParameters getTlsClientParameters() {
168         return tlsClientParameters;
169     }
170 
171     public JAXRSClientFactoryBean getRestClientFactoryBean() {
172         return Optional.ofNullable(restClientFactoryBean).orElseGet(this::defaultRestClientFactoryBean);
173     }
174 
175     public SyncopeClientFactoryBean setRestClientFactoryBean(final JAXRSClientFactoryBean restClientFactoryBean) {
176         this.restClientFactoryBean = restClientFactoryBean;
177         return this;
178     }
179 
180     /**
181      * Builds client instance with the given credentials.
182      * Such credentials will be used only to obtain a valid JWT in the
183      * {@link jakarta.ws.rs.core.HttpHeaders#AUTHORIZATION} header;
184      *
185      * @param username username
186      * @param password password
187      * @return client instance with the given credentials
188      */
189     public SyncopeClient create(final String username, final String password) {
190         return create(new ObtainingJWTAuthenticationHandler(username, password));
191     }
192 
193     /**
194      * Builds client instance with the given credentials.
195      * Such credentials will be used only to obtain a valid JWT in the
196      * {@link jakarta.ws.rs.core.HttpHeaders#AUTHORIZATION} header;
197      *
198      * @param username username
199      * @param password password
200      * @param otp TOTP value
201      * @return client instance with the given credentials
202      */
203     public SyncopeClient create(final String username, final String password, final String otp) {
204         return create(new ObtainingJWTAuthenticationHandler(username, password + ":" + otp));
205     }
206 
207     /**
208      * Builds client instance which will be passing the provided value in the
209      * {@link jakarta.ws.rs.core.HttpHeaders#AUTHORIZATION} request header.
210      *
211      * @param jwt value received after login, in the {@link RESTHeaders#TOKEN} response header
212      * @return client instance which will be passing the provided value in the
213      * {@link jakarta.ws.rs.core.HttpHeaders#AUTHORIZATION} request header
214      */
215     public SyncopeClient create(final String jwt) {
216         return create(new JWTAuthenticationHandler(jwt));
217     }
218 
219     /**
220      * Builds client instance with the given authentication handler.
221      *
222      * @param handler authentication handler
223      * @return client instance with the given authentication handler
224      */
225     public SyncopeClient create(final AuthenticationHandler handler) {
226         return new SyncopeClient(
227                 getRestClientFactoryBean(),
228                 getExceptionMapper(),
229                 handler,
230                 useCompression,
231                 getHttpClientPolicy(),
232                 tlsClientParameters);
233     }
234 
235     /**
236      * Builds client instance with the given anonymous credentials.
237      *
238      * @param username username
239      * @param password password
240      * @return client instance with the given credentials
241      */
242     public SyncopeAnonymousClient createAnonymous(final String username, final String password) {
243         return new SyncopeAnonymousClient(
244                 getRestClientFactoryBean(),
245                 getExceptionMapper(),
246                 new BasicAuthenticationHandler(username, password),
247                 useCompression,
248                 getHttpClientPolicy(),
249                 tlsClientParameters);
250     }
251 }