Skip to content

Commit 7d25fea

Browse files
committed
Improved websocket session handling
1 parent d3ca59b commit 7d25fea

21 files changed

Lines changed: 237 additions & 124 deletions

File tree

dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/authentication/IdentityProviderImpl.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,14 +80,14 @@ public Identity getIdentity(X509Certificate[] certificates)
8080
Organization o = localOrganization.get();
8181
Endpoint e = localEndpoint.get();
8282

83-
return new PractitionerIdentityImpl(o, e, getDsfRolesFor(p, certWrapper.thumbprint(), null, null),
84-
certWrapper, p, getPractitionerRolesFor(p, certWrapper.thumbprint(), null, null), null);
83+
return new PractitionerIdentityImpl(o, e, getDsfRolesFor(p, certWrapper.getThumbprint(), null, null),
84+
certWrapper, p, getPractitionerRolesFor(p, certWrapper.getThumbprint(), null, null), null);
8585
}
8686
else
8787
{
8888
logger.warn(
8989
"Certificate with thumbprint '{}' for '{}' unknown, not configured as local user or local organization unknown",
90-
certWrapper.thumbprint(), certWrapper.subjectDn());
90+
certWrapper.getThumbprint(), certWrapper.getSubjectDn());
9191
return null;
9292
}
9393
}

dsf-common/dsf-common-auth/src/main/java/dev/dsf/common/auth/DsfOpenIdCredentials.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,9 @@ public interface DsfOpenIdCredentials
4242
* @return <b>defaultValue</b> if no {@link String} entry with the given <b>key</b> in id-token
4343
*/
4444
String getStringClaimOrDefault(String key, String defaultValue);
45+
46+
/**
47+
* @return <code>true</code> if token not expired
48+
*/
49+
boolean isNotExpired();
4550
}

dsf-common/dsf-common-auth/src/main/java/dev/dsf/common/auth/conf/AbstractIdentity.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,12 @@ public boolean equals(Object obj)
8585
return Objects.equals(organizationIdentifierValue, ((AbstractIdentity) obj).organizationIdentifierValue);
8686
}
8787

88+
@Override
89+
public boolean isNotExpired()
90+
{
91+
return certificate != null && certificate.isNotExpired();
92+
}
93+
8894
@Override
8995
public boolean isLocalIdentity()
9096
{

dsf-common/dsf-common-auth/src/main/java/dev/dsf/common/auth/conf/AbstractIdentityProvider.java

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717

1818
import java.net.URI;
1919
import java.net.URISyntaxException;
20-
import java.security.cert.CertificateEncodingException;
21-
import java.security.cert.X509Certificate;
2220
import java.util.Arrays;
2321
import java.util.List;
2422
import java.util.Map;
@@ -260,26 +258,10 @@ protected final Optional<Practitioner> toPractitioner(X509CertificateWrapper cer
260258
if (certWrapper == null)
261259
return Optional.empty();
262260

263-
if (!thumbprints.contains(certWrapper.thumbprint()))
261+
if (!thumbprints.contains(certWrapper.getThumbprint()))
264262
return Optional.empty();
265263

266-
return toJcaX509CertificateHolder(certWrapper.certificate())
267-
.flatMap(ch -> toPractitioner(ch, certWrapper.thumbprint()));
268-
}
269-
270-
private Optional<JcaX509CertificateHolder> toJcaX509CertificateHolder(X509Certificate certificate)
271-
{
272-
try
273-
{
274-
return Optional.of(new JcaX509CertificateHolder(certificate));
275-
}
276-
catch (CertificateEncodingException e)
277-
{
278-
logger.debug("Unable to decode certificate", e);
279-
logger.warn("Unable to decode certificate: {} - {}", e.getClass().getName(), e.getMessage());
280-
281-
return Optional.empty();
282-
}
264+
return toPractitioner(certWrapper.toJcaX509CertificateHolder(), certWrapper.getThumbprint());
283265
}
284266

285267
private Optional<Practitioner> toPractitioner(JcaX509CertificateHolder certificate, String thumbprint)

dsf-common/dsf-common-auth/src/main/java/dev/dsf/common/auth/conf/Identity.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ public interface Identity extends Principal
2727
String ORGANIZATION_IDENTIFIER_SYSTEM = "http://dsf.dev/sid/organization-identifier";
2828
String ENDPOINT_IDENTIFIER_SYSTEM = "http://dsf.dev/sid/endpoint-identifier";
2929

30+
/**
31+
* @return <code>true</code> if credentials are not expired
32+
*/
33+
boolean isNotExpired();
34+
3035
boolean isLocalIdentity();
3136

3237
/**
@@ -38,6 +43,10 @@ public interface Identity extends Principal
3843

3944
Set<DsfRole> getDsfRoles();
4045

46+
/**
47+
* @param role
48+
* @return <code>true</code> if Identity has the given role
49+
*/
4150
boolean hasDsfRole(DsfRole role);
4251

4352
/**

dsf-common/dsf-common-auth/src/main/java/dev/dsf/common/auth/conf/PractitionerIdentityImpl.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,15 @@ public boolean equals(Object obj)
100100
&& Objects.equals(practitionerIdentifierValue, other.practitionerIdentifierValue);
101101
}
102102

103+
@Override
104+
public boolean isNotExpired()
105+
{
106+
if (credentials != null)
107+
return credentials.isNotExpired();
108+
else
109+
return super.isNotExpired();
110+
}
111+
103112
@Override
104113
public String getName()
105114
{

dsf-common/dsf-common-auth/src/main/java/dev/dsf/common/auth/conf/X509CertificateWrapper.java

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,31 @@
1919
import java.security.NoSuchAlgorithmException;
2020
import java.security.cert.CertificateEncodingException;
2121
import java.security.cert.X509Certificate;
22+
import java.time.Instant;
2223

2324
import javax.security.auth.x500.X500Principal;
2425

2526
import org.apache.commons.codec.binary.Hex;
27+
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
2628

27-
public record X509CertificateWrapper(X509Certificate certificate, String thumbprint, String subjectDn)
29+
public class X509CertificateWrapper
2830
{
31+
private final X509Certificate certificate;
32+
private final String thumbprint;
33+
private final String subjectDn;
34+
35+
private final Instant expiration;
36+
2937
public X509CertificateWrapper(X509Certificate certificate)
3038
{
31-
this(certificate, getThumbprint(certificate), getSubjectDn(certificate));
39+
this.certificate = certificate;
40+
this.thumbprint = toThumbprint(certificate);
41+
this.subjectDn = toSubjectDn(certificate);
42+
43+
this.expiration = certificate == null ? null : certificate.getNotAfter().toInstant();
3244
}
3345

34-
private static String getThumbprint(X509Certificate certificate)
46+
private static String toThumbprint(X509Certificate certificate)
3547
{
3648
try
3749
{
@@ -44,8 +56,40 @@ private static String getThumbprint(X509Certificate certificate)
4456
}
4557
}
4658

47-
private static String getSubjectDn(X509Certificate certificate)
59+
private static String toSubjectDn(X509Certificate certificate)
4860
{
4961
return certificate.getSubjectX500Principal().getName(X500Principal.RFC1779);
5062
}
63+
64+
public X509Certificate getCertificate()
65+
{
66+
return certificate;
67+
}
68+
69+
public String getThumbprint()
70+
{
71+
return thumbprint;
72+
}
73+
74+
public String getSubjectDn()
75+
{
76+
return subjectDn;
77+
}
78+
79+
public JcaX509CertificateHolder toJcaX509CertificateHolder()
80+
{
81+
try
82+
{
83+
return new JcaX509CertificateHolder(certificate);
84+
}
85+
catch (CertificateEncodingException e)
86+
{
87+
throw new RuntimeException(e);
88+
}
89+
}
90+
91+
public boolean isNotExpired()
92+
{
93+
return expiration != null && Instant.now().isBefore(expiration);
94+
}
5195
}

dsf-common/dsf-common-auth/src/main/java/dev/dsf/common/auth/logging/CurrentUserMdcLogger.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ protected void before(OrganizationIdentity organization)
5050
{
5151
before((Identity) organization);
5252

53-
organization.getCertificate().map(X509CertificateWrapper::thumbprint)
53+
organization.getCertificate().map(X509CertificateWrapper::getThumbprint)
5454
.ifPresent(t -> MDC.put(DSF_ORGANIZATION_THUMBPRINT, t));
55-
organization.getCertificate().map(X509CertificateWrapper::subjectDn)
55+
organization.getCertificate().map(X509CertificateWrapper::getSubjectDn)
5656
.ifPresent(d -> MDC.put(DSF_ORGANIZATION_DN, d));
5757

5858
MDC.put(DSF_ORGANIZATION_IDENTIFIER, organization.getOrganizationIdentifierValue());
@@ -64,9 +64,9 @@ protected void before(PractitionerIdentity practitioner)
6464
{
6565
before((Identity) practitioner);
6666

67-
practitioner.getCertificate().map(X509CertificateWrapper::thumbprint)
67+
practitioner.getCertificate().map(X509CertificateWrapper::getThumbprint)
6868
.ifPresent(t -> MDC.put(DSF_PRACTITIONER_THUMBPRINT, t));
69-
practitioner.getCertificate().map(X509CertificateWrapper::subjectDn)
69+
practitioner.getCertificate().map(X509CertificateWrapper::getSubjectDn)
7070
.ifPresent(d -> MDC.put(DSF_PRACTITIONER_DN, d));
7171
practitioner.getCredentials().map(DsfOpenIdCredentials::getUserId)
7272
.ifPresent(i -> MDC.put(DSF_PRACTITIONER_SUB, i));

dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/auth/DsfOpenIdCredentialsImpl.java

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package dev.dsf.common.auth;
1717

18+
import java.time.Instant;
1819
import java.util.Collections;
1920
import java.util.Map;
2021

@@ -29,16 +30,26 @@ public class DsfOpenIdCredentialsImpl implements DsfOpenIdCredentials
2930
private final Map<String, Object> idToken;
3031
private final Map<String, Object> accessToken;
3132

33+
private final Instant expiration;
34+
3235
public DsfOpenIdCredentialsImpl(OpenIdCredentials credentials)
3336
{
34-
this.idToken = JwtDecoder.decode((String) credentials.getResponse().get(ID_TOKEN));
35-
this.accessToken = JwtDecoder.decode((String) credentials.getResponse().get(ACCESS_TOKEN));
37+
this(JwtDecoder.decode((String) credentials.getResponse().get(ID_TOKEN)),
38+
JwtDecoder.decode((String) credentials.getResponse().get(ACCESS_TOKEN)));
3639
}
3740

3841
public DsfOpenIdCredentialsImpl(String accessToken)
3942
{
40-
this.idToken = Map.of();
41-
this.accessToken = JwtDecoder.decode(accessToken);
43+
this(Map.of(), JwtDecoder.decode(accessToken));
44+
}
45+
46+
private DsfOpenIdCredentialsImpl(Map<String, Object> idToken, Map<String, Object> accessToken)
47+
{
48+
this.idToken = idToken;
49+
this.accessToken = accessToken;
50+
51+
Long exp = getLongClaim("exp");
52+
expiration = exp == null ? null : Instant.ofEpochSecond(exp);
4253
}
4354

4455
@Override
@@ -72,4 +83,10 @@ public String getStringClaimOrDefault(String key, String defaultValue)
7283
Object o = getAccessToken().getOrDefault(key, defaultValue);
7384
return o instanceof String s ? s : defaultValue;
7485
}
86+
87+
@Override
88+
public boolean isNotExpired()
89+
{
90+
return expiration != null && Instant.now().isBefore(expiration);
91+
}
7592
}

dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/auth/DsfOpenIdLoginService.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,7 @@ public boolean validate(UserIdentity user)
6363
return false;
6464
}
6565

66-
long expiry = identity.getCredentials().get().getLongClaim("exp");
67-
long currentTimeSeconds = (long) (System.currentTimeMillis() / 1000F);
68-
if (currentTimeSeconds > expiry)
66+
if (!identity.isNotExpired())
6967
{
7068
logger.debug("ID Token has expired");
7169
return false;

0 commit comments

Comments
 (0)