Skip to content

Commit 60572e2

Browse files
committed
Added async ticket request using newer APIs
1 parent 2559c2b commit 60572e2

File tree

3 files changed

+142
-96
lines changed

3 files changed

+142
-96
lines changed

src/main/java/TicketAPI/TicketRequest.java

Lines changed: 74 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,12 @@
2525
import java.io.InputStreamReader;
2626
import java.io.OutputStreamWriter;
2727
import java.net.MalformedURLException;
28+
import java.net.URI;
2829
import java.net.URL;
30+
import java.net.http.HttpClient;
31+
import java.net.http.HttpClient.Redirect;
32+
import java.net.http.HttpRequest;
33+
import java.net.http.HttpResponse;
2934
import java.security.KeyManagementException;
3035
import java.security.KeyStore;
3136
import java.security.KeyStoreException;
@@ -35,12 +40,14 @@
3540
import java.security.cert.CertificateException;
3641
import java.security.cert.CertificateFactory;
3742
import java.security.cert.X509Certificate;
43+
import java.time.Duration;
3844
import java.util.Optional;
45+
import java.util.Properties;
46+
import java.util.concurrent.CompletableFuture;
3947
import javax.net.ssl.HttpsURLConnection;
4048
import javax.net.ssl.KeyManagerFactory;
4149
import javax.net.ssl.SSLContext;
4250
import javax.net.ssl.SSLSession;
43-
import javax.net.ssl.SSLSocketFactory;
4451
import javax.net.ssl.TrustManagerFactory;
4552

4653
/**
@@ -49,15 +56,14 @@
4956
* standard certificates exported from Qlik Sense without needing to convert them to
5057
* Java KeyStore (*.jks) certificates.
5158
*
52-
* @version 1.0
59+
* @version 1.1
5360
* @author Steven Jenkins De Haro
5461
*/
5562
public class TicketRequest {
5663

5764
private static final String XRFKEY = "1234567890123456"; // Xrfkey to prevent CSRF attacks.
5865
private static final String PROTOCOL = "TLS";
59-
private final String _hostname;
60-
private final String _vpPrefix;
66+
private final String _apiUrl;
6167
private final String _clientCertPath; // Client certificate with private key.
6268
private final char[] _clientCertPassword;
6369
private final String _rootCertPath; // Required in this example because Qlik Sense certs are used.
@@ -74,8 +80,8 @@ public TicketRequest(String hostname, Optional<String> virtualProxyPrefix,
7480
String clientCertPath, char[] clientCertPassword,
7581
String rootCertPath) {
7682

77-
_hostname = hostname;
78-
_vpPrefix = virtualProxyPrefix.isPresent() ? "/" + virtualProxyPrefix.get() : "";
83+
_apiUrl = String.format("https://%1$s:4243/qps%2$s/ticket?xrfkey=%3$s",
84+
hostname, virtualProxyPrefix.isPresent() ? "/" + virtualProxyPrefix.get() : "", XRFKEY);
7985
_clientCertPath = clientCertPath;
8086
_clientCertPassword = clientCertPassword;
8187
_rootCertPath = rootCertPath;
@@ -85,15 +91,15 @@ public TicketRequest(String hostname, Optional<String> virtualProxyPrefix,
8591
* Configures the needed certificates to validate the identity of the HTTPS
8692
* server against a list of trusted certificates and to authenticate to the
8793
* HTTPS server using a private key.
88-
* @return A layered socket factory for TLS/SSL connections.
94+
* @return An initialized secure socket context for TLS/SSL connections.
8995
* @throws KeyStoreException
9096
* @throws IOException
9197
* @throws CertificateException
9298
* @throws NoSuchAlgorithmException
9399
* @throws UnrecoverableKeyException
94100
* @throws KeyManagementException
95101
*/
96-
private SSLSocketFactory getSSLSocketFactory()
102+
private SSLContext getSSLContext()
97103
throws KeyStoreException, IOException, CertificateException,
98104
NoSuchAlgorithmException, UnrecoverableKeyException,
99105
KeyManagementException {
@@ -108,7 +114,7 @@ private SSLSocketFactory getSSLSocketFactory()
108114
tmf.init(trustStore);
109115
context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
110116

111-
return context.getSocketFactory();
117+
return context;
112118
}
113119

114120
/**
@@ -162,21 +168,20 @@ public String getTicket(String userDirectory, String userId)
162168
CertificateException, NoSuchAlgorithmException,
163169
UnrecoverableKeyException, KeyManagementException {
164170

165-
var apiUrl = String.format("https://%1$s:4243/qps%2$s/ticket?xrfkey=%3$s", _hostname, _vpPrefix, XRFKEY);
166171
var jsonRequestBody = String.format("{ 'UserId':'%1$s','UserDirectory':'%2$s','Attributes': [] }",
167172
userId, userDirectory);
168-
var url = new URL(apiUrl);
173+
var url = new URL(_apiUrl);
169174
var connection = (HttpsURLConnection) url.openConnection();
170-
175+
171176
/*
172-
* When target hostname is not listed in server's certificate SAN field,
173-
* use this as a whitelist for exceptions to continue. For example,
174-
* hostname.equals("xx.xx.xx.xx" or "localhost") ? true : false
175-
* See https://support.qlik.com/articles/000078616 for more info.
177+
* When target hostname is not listed in server's certificate SAN field,
178+
* use this as a whitelist for exceptions to continue. For example,
179+
* hostname.equals("xx.xx.xx.xx" or "localhost") ? true : false
180+
* See https://support.qlik.com/articles/000078616 for more info.
176181
*/
177182
HttpsURLConnection.setDefaultHostnameVerifier((String hostname, SSLSession session) -> true);
178-
179-
connection.setSSLSocketFactory(getSSLSocketFactory());
183+
184+
connection.setSSLSocketFactory(getSSLContext().getSocketFactory());
180185
connection.setDoOutput(true);
181186
connection.setDoInput(true);
182187
connection.setConnectTimeout(30000);
@@ -192,15 +197,62 @@ public String getTicket(String userDirectory, String userId)
192197
}
193198

194199
var sb = new StringBuilder();
195-
200+
196201
// Gets the response from the QPS BufferedReader.
197-
try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
202+
try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
198203
String inputLine;
199204
while ((inputLine = in.readLine()) != null) {
200205
sb.append(inputLine);
201206
}
202207
}
203-
208+
204209
return sb.toString();
205-
}
210+
}
211+
212+
/**
213+
* Requests a ticket asynchronously from the Qlik Sense Proxy Service that is valid for one minute.Note: This function uses a more modern API than the {@link #getTicket(String, String) getTicket} function.
214+
* @param userDirectory Directory associated with user.
215+
* @param userId Login name of user.
216+
* @return CompletableFuture with Ticket to claim within one minute.
217+
* @throws MalformedURLException
218+
* @throws IOException
219+
* @throws KeyStoreException
220+
* @throws CertificateException
221+
* @throws NoSuchAlgorithmException
222+
* @throws UnrecoverableKeyException
223+
* @throws KeyManagementException
224+
*/
225+
public CompletableFuture<String> getTicketAsync(String userDirectory, String userId)
226+
throws MalformedURLException, IOException, KeyStoreException,
227+
CertificateException, NoSuchAlgorithmException,
228+
UnrecoverableKeyException, KeyManagementException {
229+
230+
var jsonRequestBody = String.format("{ 'UserId':'%1$s','UserDirectory':'%2$s','Attributes': [] }",
231+
userId, userDirectory);
232+
final Properties props = System.getProperties();
233+
234+
/*
235+
* Disables hostname validation when hostname is not listed in server's
236+
* certificate SAN field.
237+
*/
238+
props.setProperty("jdk.internal.httpclient.disableHostnameVerification", Boolean.TRUE.toString());
239+
240+
var client = HttpClient.newBuilder()
241+
.connectTimeout(Duration.ofSeconds(30))
242+
.followRedirects(Redirect.NORMAL)
243+
.sslContext(getSSLContext())
244+
.build();
245+
246+
var request = HttpRequest.newBuilder()
247+
.uri(URI.create(_apiUrl))
248+
.timeout(Duration.ofSeconds(30))
249+
.header("X-Qlik-xrfkey", XRFKEY)
250+
.header("Content-Type", "application/json")
251+
.header("Accept", "application/json")
252+
.POST(HttpRequest.BodyPublishers.ofString(jsonRequestBody))
253+
.build();
254+
255+
return client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
256+
.thenApply(response -> response.body());
257+
}
206258
}

src/main/java/TicketAPI/TicketRequestDemo.form

Lines changed: 31 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -35,48 +35,41 @@
3535
<EmptySpace max="32767" attributes="0"/>
3636
</Group>
3737
<Group type="102" attributes="0">
38+
<EmptySpace max="-2" attributes="0"/>
3839
<Group type="103" groupAlignment="0" attributes="0">
39-
<Group type="102" alignment="0" attributes="0">
40+
<Component id="jLabel7" alignment="0" max="32767" attributes="0"/>
41+
<Component id="txtClientPassword" alignment="0" max="32767" attributes="0"/>
42+
<Group type="102" alignment="1" attributes="0">
43+
<Component id="txtClientCertPath" max="32767" attributes="0"/>
4044
<EmptySpace max="-2" attributes="0"/>
41-
<Group type="103" groupAlignment="0" attributes="0">
42-
<Component id="jLabel7" alignment="0" max="32767" attributes="0"/>
43-
<Component id="txtClientPassword" alignment="0" max="32767" attributes="0"/>
44-
<Group type="102" alignment="1" attributes="0">
45-
<Component id="txtClientCertPath" max="32767" attributes="0"/>
46-
<EmptySpace max="-2" attributes="0"/>
47-
<Component id="btnClientBrowse" min="-2" pref="78" max="-2" attributes="0"/>
48-
</Group>
49-
<Group type="102" alignment="0" attributes="0">
50-
<Group type="103" groupAlignment="1" attributes="0">
51-
<Component id="jLabel8" alignment="0" max="32767" attributes="0"/>
52-
<Component id="txtRootCertPath" alignment="1" max="32767" attributes="0"/>
53-
</Group>
54-
<EmptySpace max="-2" attributes="0"/>
55-
<Component id="btnRootBrowse" min="-2" pref="78" max="-2" attributes="0"/>
56-
</Group>
57-
<Component id="lblTicket" alignment="0" max="32767" attributes="0"/>
58-
<Component id="jScrollPane1" alignment="0" max="32767" attributes="0"/>
59-
<Group type="102" alignment="0" attributes="0">
60-
<Group type="103" groupAlignment="0" max="-2" attributes="0">
61-
<Component id="txtHostname" alignment="0" max="32767" attributes="0"/>
62-
<Component id="txtDirectory" alignment="0" max="32767" attributes="0"/>
63-
<Component id="jLabel1" alignment="0" max="32767" attributes="0"/>
64-
<Component id="jLabel4" alignment="0" min="-2" pref="183" max="-2" attributes="0"/>
65-
</Group>
66-
<EmptySpace type="unrelated" max="32767" attributes="0"/>
67-
<Group type="103" groupAlignment="0" max="-2" attributes="0">
68-
<Component id="txtVirtualProxy" alignment="0" max="32767" attributes="0"/>
69-
<Component id="txtUserId" alignment="0" max="32767" attributes="0"/>
70-
<Component id="jLabel3" alignment="0" max="32767" attributes="0"/>
71-
<Component id="jLabel2" alignment="0" min="-2" pref="183" max="-2" attributes="0"/>
72-
</Group>
73-
</Group>
74-
</Group>
45+
<Component id="btnClientBrowse" min="-2" pref="78" max="-2" attributes="0"/>
7546
</Group>
7647
<Group type="102" alignment="0" attributes="0">
48+
<Group type="103" groupAlignment="1" attributes="0">
49+
<Component id="jLabel8" alignment="0" max="32767" attributes="0"/>
50+
<Component id="txtRootCertPath" alignment="1" max="32767" attributes="0"/>
51+
</Group>
7752
<EmptySpace max="-2" attributes="0"/>
78-
<Component id="jLabel6" max="32767" attributes="0"/>
53+
<Component id="btnRootBrowse" min="-2" pref="78" max="-2" attributes="0"/>
54+
</Group>
55+
<Component id="lblTicket" alignment="0" max="32767" attributes="0"/>
56+
<Component id="jScrollPane1" alignment="0" max="32767" attributes="0"/>
57+
<Group type="102" alignment="0" attributes="0">
58+
<Group type="103" groupAlignment="0" max="-2" attributes="0">
59+
<Component id="txtHostname" alignment="0" max="32767" attributes="0"/>
60+
<Component id="txtDirectory" alignment="0" max="32767" attributes="0"/>
61+
<Component id="jLabel1" alignment="0" max="32767" attributes="0"/>
62+
<Component id="jLabel4" alignment="0" min="-2" pref="183" max="-2" attributes="0"/>
63+
</Group>
64+
<EmptySpace type="unrelated" max="32767" attributes="0"/>
65+
<Group type="103" groupAlignment="0" max="-2" attributes="0">
66+
<Component id="txtVirtualProxy" alignment="0" max="32767" attributes="0"/>
67+
<Component id="txtUserId" alignment="0" max="32767" attributes="0"/>
68+
<Component id="jLabel3" alignment="0" max="32767" attributes="0"/>
69+
<Component id="jLabel2" alignment="0" min="-2" pref="183" max="-2" attributes="0"/>
70+
</Group>
7971
</Group>
72+
<Component id="jLabel6" alignment="0" max="32767" attributes="0"/>
8073
</Group>
8174
<EmptySpace max="-2" attributes="0"/>
8275
</Group>
@@ -217,7 +210,7 @@
217210
</Component>
218211
<Component class="javax.swing.JLabel" name="jLabel2">
219212
<Properties>
220-
<Property name="text" type="java.lang.String" value="Virtual Proxy Prefix:"/>
213+
<Property name="text" type="java.lang.String" value="Virtual Proxy Prefix (Optional):"/>
221214
</Properties>
222215
</Component>
223216
<Component class="javax.swing.JTextField" name="txtClientCertPath">
@@ -254,7 +247,7 @@
254247
</Component>
255248
<Component class="javax.swing.JLabel" name="jLabel4">
256249
<Properties>
257-
<Property name="text" type="java.lang.String" value="Directory:"/>
250+
<Property name="text" type="java.lang.String" value="User Directory:"/>
258251
</Properties>
259252
</Component>
260253
<Component class="javax.swing.JSeparator" name="jSeparator1">

0 commit comments

Comments
 (0)