Skip to content

Commit be8c2ab

Browse files
Return login expiration times from tyler auth response (#402)
Tyler already sends back an `ExpirationDateTime` in the auth response - we just weren't passing it through. Added a `LoginResult` record to carry both the tokens and expiry out of TylerLogin, threaded it through `SecurityHub`, and surfaced it as a separate expirationTimes field in `NewTokens` so clients can see it in the response. Updated `SecurityHubTest` to set the expiry on the mock and assert it comes back correctly.
1 parent 39318e0 commit be8c2ab

6 files changed

Lines changed: 53 additions & 16 deletions

File tree

proxyserver/src/main/java/edu/suffolk/litlab/efsp/db/model/NewTokens.java

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,38 +11,51 @@
1111
public class NewTokens {
1212

1313
@JsonUnwrapped private final Map<String, String> tokens;
14+
private final Map<String, String> expirationTimes;
1415

1516
public NewTokens() {
1617
this.tokens = Map.of();
18+
this.expirationTimes = Map.of();
1719
}
1820

1921
public NewTokens(Map<String, String> tokens) {
2022
this.tokens = tokens;
23+
this.expirationTimes = Map.of();
24+
}
25+
26+
public NewTokens(Map<String, String> tokens, Map<String, String> expirationTimes) {
27+
this.tokens = tokens;
28+
this.expirationTimes = expirationTimes;
2129
}
2230

2331
public Map<String, String> getTokens() {
2432
return tokens;
2533
}
2634

35+
public Map<String, String> getExpirationTimes() {
36+
return expirationTimes;
37+
}
38+
2739
@Override
2840
public boolean equals(Object other) {
2941
if (other == null) {
3042
return false;
3143
}
3244
if (other instanceof NewTokens otherTokens) {
33-
return tokens.equals(otherTokens.tokens);
45+
return tokens.equals(otherTokens.tokens)
46+
&& expirationTimes.equals(otherTokens.expirationTimes);
3447
} else {
3548
return false;
3649
}
3750
}
3851

3952
@Override
4053
public int hashCode() {
41-
return tokens.hashCode();
54+
return tokens.hashCode() * 31 + expirationTimes.hashCode();
4255
}
4356

4457
@Override
4558
public String toString() {
46-
return tokens.toString();
59+
return "tokens=" + tokens + ", expirationTimes=" + expirationTimes;
4760
}
4861
}

proxyserver/src/main/java/edu/suffolk/litlab/efsp/server/auth/LoginInterface.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
package edu.suffolk.litlab.efsp.server.auth;
22

33
import com.fasterxml.jackson.databind.JsonNode;
4-
import java.util.Map;
54
import java.util.Optional;
65

76
public interface LoginInterface {
87

9-
Optional<Map<String, String>> login(JsonNode loginInfo);
8+
Optional<LoginResult> login(JsonNode loginInfo);
109

1110
String getLoginName();
1211

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package edu.suffolk.litlab.efsp.server.auth;
2+
3+
import java.util.Map;
4+
import java.util.Optional;
5+
6+
public record LoginResult(Map<String, String> tokens, Optional<String> expirationDateTime) {}

proxyserver/src/main/java/edu/suffolk/litlab/efsp/server/auth/SecurityHub.java

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public class SecurityHub {
3131
private static final Logger log = LoggerFactory.getLogger(SecurityHub.class);
3232

3333
private final List<LoginInterface> tylerLoginObjs;
34-
private final Map<String, Function<JsonNode, Optional<Map<String, String>>>> loginFunctions;
34+
private final Map<String, Function<JsonNode, Optional<LoginResult>>> loginFunctions;
3535
private final Supplier<LoginDatabase> ldSupplier;
3636

3737
/**
@@ -49,7 +49,10 @@ public SecurityHub(Supplier<LoginDatabase> ldSupplier, List<Jurisdiction> jurisd
4949
}
5050

5151
this.loginFunctions = new HashMap<>();
52-
this.loginFunctions.put("jeffnet", info -> Optional.of(Map.of("JEFFNET-TOKEN", "deprecated")));
52+
this.loginFunctions.put(
53+
"jeffnet",
54+
info ->
55+
Optional.of(new LoginResult(Map.of("JEFFNET-TOKEN", "deprecated"), Optional.empty())));
5356
this.loginFunctions.putAll(
5457
this.tylerLoginObjs.stream()
5558
.collect(Collectors.toMap(lo -> lo.getLoginName(), lo -> (info) -> lo.login(info))));
@@ -89,6 +92,7 @@ public Optional<NewTokens> login(String apiKey, JsonNode loginInfo) {
8992
return Optional.empty();
9093
}
9194
var newTokens = new HashMap<String, String>();
95+
var expirationTimes = new HashMap<String, String>();
9296
Iterable<String> orgs = loginInfo::fieldNames;
9397
for (String orgName : orgs) {
9498
orgName = orgName.toLowerCase();
@@ -111,18 +115,20 @@ public Optional<NewTokens> login(String apiKey, JsonNode loginInfo) {
111115
log.error("There is no {} to login to: enabled map: {}", permissionsName, atRest.enabled);
112116
return Optional.empty();
113117
}
114-
Optional<Map<String, String>> maybeNewTokens =
115-
loginFunctions.get(orgName).apply(loginInfo.get(orgName));
116-
if (maybeNewTokens.isEmpty()) {
118+
Optional<LoginResult> maybeResult = loginFunctions.get(orgName).apply(loginInfo.get(orgName));
119+
if (maybeResult.isEmpty()) {
117120
log.warn("Couldn't login to {}", orgName);
118121
return Optional.empty();
119122
}
120123
log.info("New tokens for {}", orgName);
121-
newTokens.putAll(maybeNewTokens.get());
124+
LoginResult result = maybeResult.get();
125+
newTokens.putAll(result.tokens());
126+
final String finalOrgName = orgName;
127+
result.expirationDateTime().ifPresent(expiry -> expirationTimes.put(finalOrgName, expiry));
122128
}
123129
if (newTokens.isEmpty()) {
124130
log.warn("No successful logins occurred: returning empty tokens object");
125131
}
126-
return Optional.of(new NewTokens(newTokens));
132+
return Optional.of(new NewTokens(newTokens, expirationTimes));
127133
}
128134
}

proxyserver/src/main/java/edu/suffolk/litlab/efsp/server/auth/TylerLogin.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public static String getHeaderKeyFromJurisdiction(Jurisdiction jurisdiction) {
3535
}
3636

3737
@Override
38-
public Optional<Map<String, String>> login(JsonNode loginInfo) {
38+
public Optional<LoginResult> login(JsonNode loginInfo) {
3939
if (!loginInfo.isObject()
4040
|| !loginInfo.has("username")
4141
|| !loginInfo.get("username").isTextual()) {
@@ -65,12 +65,17 @@ public Optional<Map<String, String>> login(JsonNode loginInfo) {
6565
+ authRes.getError().getErrorText());
6666
return Optional.empty();
6767
} else {
68-
return Optional.of(
68+
Map<String, String> tokens =
6969
Map.of(
7070
getHeaderKey(),
7171
authRes.getEmail() + ":" + authRes.getPasswordHash(),
7272
getHeaderId(jurisdiction),
73-
authRes.getUserID()));
73+
authRes.getUserID());
74+
Optional<String> expirationDateTime =
75+
authRes.getExpirationDateTime() != null
76+
? Optional.of(authRes.getExpirationDateTime().toString())
77+
: Optional.empty();
78+
return Optional.of(new LoginResult(tokens, expirationDateTime));
7479
}
7580
}
7681

proxyserver/src/test/java/edu/suffolk/litlab/efsp/server/auth/SecurityHubTest.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515
import edu.suffolk.litlab.efsp.tyler.TylerClients;
1616
import edu.suffolk.litlab.efsp.tyler.TylerUserClient;
1717
import edu.suffolk.litlab.efsp.tyler.TylerUserFactory;
18+
import java.util.GregorianCalendar;
1819
import java.util.List;
1920
import java.util.Map;
2021
import java.util.Optional;
22+
import javax.xml.datatype.DatatypeFactory;
2123
import org.junit.jupiter.api.AfterAll;
2224
import org.junit.jupiter.api.BeforeAll;
2325
import org.junit.jupiter.api.BeforeEach;
@@ -94,7 +96,7 @@ class TylerLoginTests {
9496
private ObjectNode loginNode;
9597

9698
@BeforeEach
97-
public void setup() {
99+
public void setup() throws Exception {
98100
AtRest atRest = new AtRest();
99101
atRest.enabled = Map.of("tyler", true, "jeffnet", false);
100102
when(ld.getAtRestInfo(API_KEY)).thenReturn(Optional.of(atRest));
@@ -106,11 +108,16 @@ public void setup() {
106108
var error = new ErrorType();
107109
error.setErrorCode("0");
108110

111+
var expiry =
112+
DatatypeFactory.newInstance()
113+
.newXMLGregorianCalendar(new GregorianCalendar(2026, 11, 31, 23, 59, 59));
114+
109115
var authResp = new AuthenticateResponseType();
110116
authResp.setEmail(EMAIL);
111117
authResp.setError(error);
112118
authResp.setUserID("abc123");
113119
authResp.setPasswordHash(PASSWORD_HASH);
120+
authResp.setExpirationDateTime(expiry);
114121
when(tylerUserClient.authenticateUser(refEq(authReq))).thenReturn(authResp);
115122

116123
tylerNode = mapper.createObjectNode();
@@ -140,6 +147,7 @@ public void testTylerLogin() throws Exception {
140147
assertThat(activeTyler).isPresent();
141148
assertThat(activeTyler.get().getTokens().get("TYLER-TOKEN-ILLINOIS"))
142149
.isEqualTo("bob@example.com:the_password_hash");
150+
assertThat(activeTyler.get().getExpirationTimes()).containsKey("tyler-illinois");
143151
Optional<NewTokens> repeatLogin = hub.login(API_KEY, loginNode);
144152
assertThat(repeatLogin).isPresent();
145153
assertThat(activeTyler.get()).isEqualTo(repeatLogin.get());

0 commit comments

Comments
 (0)