Skip to content

Commit 2629d18

Browse files
committed
update test, change naming
1 parent b43e4f8 commit 2629d18

File tree

7 files changed

+150
-191
lines changed

7 files changed

+150
-191
lines changed

src/main/java/org/privacyidea/AsyncRequestCallable.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public String call() throws Exception
6363
if (this.authTokenRequired)
6464
{
6565
// Wait for the auth token to be retrieved and add it to the header
66-
headers.put(PIConstants.HEADER_AUTHORIZATION, privacyIDEA.getAuthToken());
66+
headers.put(PIConstants.HEADER_AUTHORIZATION, privacyIDEA.getJWT());
6767
}
6868

6969
// Do the actual request

src/main/java/org/privacyidea/Endpoint.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
/**
3838
* This class handles sending requests to the server.
3939
*/
40-
class Endpoint
40+
public class Endpoint
4141
{
4242
private final PrivacyIDEA privacyIDEA;
4343
private final PIConfig piConfig;

src/main/java/org/privacyidea/JSONParser.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ LinkedHashMap<String, String> extractAuthToken(String serverResponse)
8383
int respDate = obj.getAsJsonPrimitive(TIME).getAsInt();
8484
int expDate = JsonParser.parseString(dec).getAsJsonObject().getAsJsonPrimitive(EXP).getAsInt();
8585
int difference = expDate - respDate;
86-
privacyIDEA.log("Authentication token's durability: " + difference / 60 + " minutes. Token expires at: " + new Date(expDate * 1000L));
86+
privacyIDEA.log("JWT Validity: " + difference / 60 + " minutes. Token expires at: " + new Date(expDate * 1000L));
8787

8888
return new LinkedHashMap<>(Map.of(AUTH_TOKEN, authToken, AUTH_TOKEN_EXP, String.valueOf(expDate)));
8989
}
@@ -528,4 +528,4 @@ private JsonPrimitive getPrimitiveOrNull(JsonObject obj, String name)
528528
}
529529
return primitive;
530530
}
531-
}
531+
}

src/main/java/org/privacyidea/PrivacyIDEA.java

Lines changed: 30 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@ public class PrivacyIDEA implements Closeable
3333
private final IPILogger log;
3434
private final IPISimpleLogger simpleLog;
3535
private final Endpoint endpoint;
36-
protected String authToken = null;
36+
private String jwt = null;
3737
// Thread pool for connections
3838
private final BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);
3939
private final ThreadPoolExecutor threadPool = new ThreadPoolExecutor(20, 20, 10, TimeUnit.SECONDS, queue);
4040
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
41-
private CountDownLatch authTokenLatch;
41+
private CountDownLatch jwtRetrievalLatch;
4242
final JSONParser parser;
4343
// Responses from these endpoints will not be logged. The list can be overwritten.
4444
private List<String> logExcludedEndpoints = Arrays.asList(PIConstants.ENDPOINT_AUTH,
@@ -54,7 +54,7 @@ private PrivacyIDEA(PIConfig configuration, IPILogger logger, IPISimpleLogger si
5454
this.threadPool.allowCoreThreadTimeOut(true);
5555
if (serviceAccountAvailable())
5656
{
57-
retrieveAuthToken();
57+
retrieveJWT();
5858
}
5959
}
6060

@@ -364,53 +364,58 @@ private void appendRealm(Map<String, String> params)
364364
}
365365

366366
/**
367-
* Retrieve the auth token from the /auth endpoint and schedule the next retrieval.
367+
* Retrieve the JWT from the /auth endpoint and schedule the next retrieval.
368368
*/
369-
private void retrieveAuthToken()
369+
private void retrieveJWT()
370370
{
371371
try
372372
{
373-
authTokenLatch = new CountDownLatch(1);
373+
this.jwtRetrievalLatch = new CountDownLatch(1);
374374
String response = runRequestAsync(ENDPOINT_AUTH, serviceAccountParam(), Collections.emptyMap(), false, POST);
375375
LinkedHashMap<String, String> authTokenMap = parser.extractAuthToken(response);
376-
this.authToken = authTokenMap.get(AUTH_TOKEN);
376+
this.jwt = authTokenMap.get(AUTH_TOKEN);
377377
long authTokenExp = Integer.parseInt(authTokenMap.get(AUTH_TOKEN_EXP));
378378

379379
// Schedule the next token retrieval to 1 min before expiration
380380
long delay = Math.max(1, authTokenExp - 60 - (System.currentTimeMillis() / 1000L));
381-
scheduler.schedule(this::retrieveAuthToken, delay, TimeUnit.SECONDS);
382-
381+
this.scheduler.schedule(this::retrieveJWT, delay, TimeUnit.SECONDS);
382+
log("Next JWT retrieval in " + delay + " seconds.");
383383
// Count down the latch to indicate that the token is retrieved
384-
authTokenLatch.countDown();
384+
this.jwtRetrievalLatch.countDown();
385385
}
386386
catch (Exception e)
387387
{
388388
error("Failed to retrieve auth token: " + e.getMessage());
389-
authTokenLatch.countDown();
389+
this.jwtRetrievalLatch.countDown();
390390
}
391391
}
392392

393393
/**
394-
* Get the auth token from the /auth endpoint using the service account.
394+
* Get the JWT from the /auth endpoint using the service account.
395395
*
396-
* @return auth token or null.
397-
* @throws InterruptedException if the thread is interrupted while waiting for the auth token.
396+
* @return JWT as string or null on error.
398397
*/
399-
public String getAuthToken() throws InterruptedException
398+
public String getJWT()
400399
{
401-
// Wait for the auth token to be retrieved
402-
authTokenLatch.await();
403-
return this.authToken;
400+
try
401+
{
402+
jwtRetrievalLatch.await();
403+
}
404+
catch (InterruptedException e)
405+
{
406+
error(e);
407+
return null;
408+
}
409+
return this.jwt;
404410
}
405411

406412
/**
407413
* @return true if a service account is available
408414
*/
409415
public boolean serviceAccountAvailable()
410416
{
411-
return configuration.serviceAccountName != null && !configuration.serviceAccountName.isEmpty()
412-
&& configuration.serviceAccountPass != null &&
413-
!configuration.serviceAccountPass.isEmpty();
417+
return configuration.serviceAccountName != null && !configuration.serviceAccountName.isEmpty() &&
418+
configuration.serviceAccountPass != null && !configuration.serviceAccountPass.isEmpty();
414419
}
415420

416421
/**
@@ -424,16 +429,15 @@ public boolean serviceAccountAvailable()
424429
* @param method http request method
425430
* @return response of the server as string or null
426431
*/
427-
private String runRequestAsync(String path, Map<String, String> params, Map<String, String> headers,
428-
boolean authTokenRequired,
432+
private String runRequestAsync(String path, Map<String, String> params, Map<String, String> headers, boolean authTokenRequired,
429433
String method)
430434
{
431435
if (!configuration.forwardClientIP.isEmpty())
432436
{
433437
params.put(CLIENT_IP, configuration.forwardClientIP);
434438
}
435-
Callable<String> callable = new AsyncRequestCallable(this, endpoint, path, params, headers, authTokenRequired, method);
436-
Future<String> future = threadPool.submit(callable);
439+
Callable<String> callable = new AsyncRequestCallable(this, this.endpoint, path, params, headers, authTokenRequired, method);
440+
Future<String> future = this.threadPool.submit(callable);
437441
String response = null;
438442
try
439443
{
@@ -535,10 +539,6 @@ else if (this.simpleLog != null)
535539
{
536540
this.simpleLog.piLog(message);
537541
}
538-
else
539-
{
540-
System.out.println(message);
541-
}
542542
}
543543
}
544544

@@ -559,10 +559,6 @@ else if (this.simpleLog != null)
559559
{
560560
this.simpleLog.piLog(e.getMessage());
561561
}
562-
else
563-
{
564-
System.out.println(e.getLocalizedMessage());
565-
}
566562
}
567563
}
568564

@@ -743,7 +739,7 @@ public Builder proxy(String proxyHost, int proxyPort)
743739

744740
/**
745741
* Build the PrivacyIDEA instance with the set parameters.
746-
*
742+
* If a service account is set, the JWT retrieval is done immediately.
747743
* @return PrivacyIDEA instance
748744
*/
749745
public PrivacyIDEA build()

src/test/java/org/privacyidea/TestGetTokenInfo.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package org.privacyidea;
1818

1919
import java.util.ArrayList;
20-
import java.util.LinkedHashMap;
2120
import java.util.List;
2221
import org.junit.After;
2322
import org.junit.Before;
@@ -100,7 +99,7 @@ public void testSuccess() throws InterruptedException
10099
assertEquals("defrealm", tokenInfo.userRealm);
101100
assertEquals("Test", tokenInfo.username);
102101

103-
assertEquals(authToken, privacyIDEA.getAuthToken());
102+
assertEquals(authToken, privacyIDEA.getJWT());
104103
}
105104

106105
@Test
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package org.privacyidea;
2+
3+
import com.auth0.jwt.JWT;
4+
import com.auth0.jwt.algorithms.Algorithm;
5+
import java.io.IOException;
6+
import java.util.concurrent.TimeUnit;
7+
import org.junit.After;
8+
import org.junit.Before;
9+
import org.junit.Test;
10+
import org.mockserver.integration.ClientAndServer;
11+
import org.mockserver.model.Delay;
12+
import org.mockserver.model.HttpRequest;
13+
import org.mockserver.model.HttpResponse;
14+
15+
import java.util.Date;
16+
17+
import static org.junit.Assert.assertEquals;
18+
19+
public class TestJWT extends PILogImplementation implements org.mockserver.mock.action.ExpectationResponseCallback
20+
{
21+
private ClientAndServer mockServer;
22+
private String jwt;
23+
private int jwtExpirationTimeMs = 3000;
24+
private int mockServerResponseDelayMs = 2000;
25+
private int testIterations = 3;
26+
27+
private final String serviceAccount = "admin";
28+
private final String servicePassword = "admin";
29+
private PrivacyIDEA privacyIDEA;
30+
31+
32+
@Before
33+
public void setup()
34+
{
35+
this.mockServer = ClientAndServer.startClientAndServer(1080);
36+
this.mockServer.when(HttpRequest.request()
37+
.withPath(PIConstants.ENDPOINT_AUTH)
38+
.withMethod("POST")
39+
.withBody("username=" + serviceAccount + "&password=" + servicePassword))
40+
.respond(this, new Delay(TimeUnit.MILLISECONDS, this.mockServerResponseDelayMs));
41+
// When build() is called with a service account set, a jwt retrieval is attempted immediately, therefore, the mock server has to
42+
// be ready
43+
this.privacyIDEA = PrivacyIDEA.newBuilder("https://127.0.0.1:1080", "test")
44+
.serviceAccount(this.serviceAccount, this.servicePassword)
45+
.httpTimeoutMs(15000)
46+
//.logger(this)
47+
.verifySSL(false)
48+
.build();
49+
}
50+
51+
@Test
52+
public void testSuccess() throws IOException
53+
{
54+
for (int i = 0; i < this.testIterations; i++)
55+
{
56+
assertEquals(this.jwt, privacyIDEA.getJWT());
57+
try
58+
{
59+
Thread.sleep(this.jwtExpirationTimeMs);
60+
}
61+
catch (InterruptedException e)
62+
{
63+
Thread.currentThread().interrupt();
64+
}
65+
}
66+
// Wait for the last connection to finish before closing
67+
try
68+
{
69+
Thread.sleep(this.mockServerResponseDelayMs);
70+
}
71+
catch (InterruptedException e)
72+
{
73+
throw new RuntimeException(e);
74+
}
75+
privacyIDEA.close();
76+
}
77+
78+
@After
79+
public void tearDown()
80+
{
81+
mockServer.stop();
82+
}
83+
84+
private String generateJWT(long validityMs)
85+
{
86+
//log("JWT expiration date: " + new Date(System.currentTimeMillis() + validityMs));
87+
return JWT.create()
88+
.withSubject("testUser")
89+
.withIssuer("testIssuer")
90+
.withExpiresAt(new Date(System.currentTimeMillis() + validityMs))
91+
.sign(Algorithm.HMAC256("testSecret"));
92+
}
93+
94+
private String postAuthSuccessResponse(String jwt)
95+
{
96+
return "{\n" + " \"id\": 1,\n" + " \"jsonrpc\": \"2.0\",\n" + " \"result\": {\n" + " \"status\": true,\n" +
97+
" \"value\": {\n" + " \"log_level\": 20,\n" + " \"menus\": [\n" +
98+
" \"components\",\n" + " \"machines\"\n" + " ],\n" +
99+
" \"realm\": \"\",\n" + " \"rights\": [\n" + " \"policydelete\",\n" +
100+
" \"resync\"\n" + " ],\n" + " \"role\": \"admin\",\n" + " \"token\": \"" +
101+
jwt + "\",\n" + " \"username\": \"admin\",\n" + " \"logout_time\": 120,\n" +
102+
" \"default_tokentype\": \"hotp\",\n" + " \"user_details\": false,\n" +
103+
" \"subscription_status\": 0\n" + " }\n" + " },\n" + " \"time\": " +
104+
(System.currentTimeMillis() / 1000L) + ",\n" + " \"version\": \"privacyIDEA 3.2.1\",\n" +
105+
" \"versionnumber\": \"3.2.1\",\n" + " \"signature\": \"rsa_sha256_pss:\"\n" + "}";
106+
}
107+
108+
@Override
109+
public HttpResponse handle(HttpRequest httpRequest) throws Exception
110+
{
111+
// The next retrieval is always scheduled for 1 minute before expiration
112+
this.jwt = generateJWT(60000 + this.jwtExpirationTimeMs);
113+
return HttpResponse.response().withBody(postAuthSuccessResponse(this.jwt));
114+
}
115+
}

0 commit comments

Comments
 (0)