diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/authentication/IdentityProviderImpl.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/authentication/IdentityProviderImpl.java index 551d13ea1..845c936ea 100644 --- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/authentication/IdentityProviderImpl.java +++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/authentication/IdentityProviderImpl.java @@ -32,6 +32,7 @@ import dev.dsf.common.auth.conf.IdentityProvider; import dev.dsf.common.auth.conf.PractitionerIdentityImpl; import dev.dsf.common.auth.conf.RoleConfig; +import dev.dsf.common.auth.conf.X509CertificateWrapper; public class IdentityProviderImpl extends AbstractIdentityProvider implements IdentityProvider, InitializingBean @@ -68,9 +69,9 @@ public Identity getIdentity(X509Certificate[] certificates) if (certificates == null || certificates.length == 0) return null; - String thumbprint = getThumbprint(certificates[0]); + X509CertificateWrapper certWrapper = new X509CertificateWrapper(certificates[0]); - Optional practitioner = toPractitioner(certificates[0]); + Optional practitioner = toPractitioner(certWrapper); Optional localOrganization = organizationAndEndpointProvider.getLocalOrganization(); Optional localEndpoint = organizationAndEndpointProvider.getLocalEndpoint(); if (practitioner.isPresent() && localOrganization.isPresent() && localEndpoint.isPresent()) @@ -79,14 +80,14 @@ public Identity getIdentity(X509Certificate[] certificates) Organization o = localOrganization.get(); Endpoint e = localEndpoint.get(); - return new PractitionerIdentityImpl(o, e, getDsfRolesFor(p, thumbprint, null, null), certificates[0], p, - getPractitionerRolesFor(p, thumbprint, null, null), null); + return new PractitionerIdentityImpl(o, e, getDsfRolesFor(p, certWrapper.thumbprint(), null, null), + certWrapper, p, getPractitionerRolesFor(p, certWrapper.thumbprint(), null, null), null); } else { logger.warn( "Certificate with thumbprint '{}' for '{}' unknown, not configured as local user or local organization unknown", - thumbprint, getDn(certificates[0])); + certWrapper.thumbprint(), certWrapper.subjectDn()); return null; } } diff --git a/dsf-common/dsf-common-auth/src/main/java/dev/dsf/common/auth/conf/AbstractIdentity.java b/dsf-common/dsf-common-auth/src/main/java/dev/dsf/common/auth/conf/AbstractIdentity.java index 9b006cedc..c3079bd49 100644 --- a/dsf-common/dsf-common-auth/src/main/java/dev/dsf/common/auth/conf/AbstractIdentity.java +++ b/dsf-common/dsf-common-auth/src/main/java/dev/dsf/common/auth/conf/AbstractIdentity.java @@ -15,7 +15,6 @@ */ package dev.dsf.common.auth.conf; -import java.security.cert.X509Certificate; import java.util.Collection; import java.util.Collections; import java.util.HashSet; @@ -35,7 +34,7 @@ public abstract class AbstractIdentity implements Identity private final Organization organization; private final Endpoint endpoint; private final Set dsfRoles = new HashSet<>(); - private final X509Certificate certificate; + private final X509CertificateWrapper certificate; /** * @param localIdentity @@ -50,7 +49,7 @@ public abstract class AbstractIdentity implements Identity * may be null */ public AbstractIdentity(boolean localIdentity, Organization organization, Endpoint endpoint, - Collection dsfRoles, X509Certificate certificate) + Collection dsfRoles, X509CertificateWrapper certificate) { this.localIdentity = localIdentity; this.organization = Objects.requireNonNull(organization, "organization"); @@ -106,7 +105,7 @@ public boolean hasDsfRole(DsfRole dsfRole) } @Override - public Optional getCertificate() + public Optional getCertificate() { // null if login via OIDC return Optional.ofNullable(certificate); diff --git a/dsf-common/dsf-common-auth/src/main/java/dev/dsf/common/auth/conf/AbstractIdentityProvider.java b/dsf-common/dsf-common-auth/src/main/java/dev/dsf/common/auth/conf/AbstractIdentityProvider.java index 4bb855f1f..68c22b425 100644 --- a/dsf-common/dsf-common-auth/src/main/java/dev/dsf/common/auth/conf/AbstractIdentityProvider.java +++ b/dsf-common/dsf-common-auth/src/main/java/dev/dsf/common/auth/conf/AbstractIdentityProvider.java @@ -17,8 +17,6 @@ import java.net.URI; import java.net.URISyntaxException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.util.Arrays; @@ -31,9 +29,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import javax.security.auth.x500.X500Principal; - -import org.apache.commons.codec.binary.Hex; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.x500.AttributeTypeAndValue; import org.bouncycastle.asn1.x500.X500Name; @@ -141,24 +136,6 @@ public final Identity getIdentity(DsfOpenIdCredentials credentials) protected abstract Optional getLocalEndpoint(); - protected final String getThumbprint(X509Certificate certificate) - { - try - { - byte[] digest = MessageDigest.getInstance("SHA-512").digest(certificate.getEncoded()); - return Hex.encodeHexString(digest); - } - catch (CertificateEncodingException | NoSuchAlgorithmException e) - { - throw new RuntimeException(e); - } - } - - protected final String getDn(X509Certificate certificate) - { - return certificate.getSubjectX500Principal().getName(X500Principal.RFC1779); - } - protected final List getGroupsFromTokens(Map parsedIdToken, Map parsedAccessToken) { @@ -295,16 +272,16 @@ private String toEmail(String iss, String sub) return sub + "." + getHost(iss) + "@oidc.invalid"; } - protected final Optional toPractitioner(X509Certificate certificate) + protected final Optional toPractitioner(X509CertificateWrapper certWrapper) { - if (certificate == null) + if (certWrapper == null) return Optional.empty(); - String thumbprint = getThumbprint(certificate); - if (!thumbprints.contains(thumbprint)) + if (!thumbprints.contains(certWrapper.thumbprint())) return Optional.empty(); - return toJcaX509CertificateHolder(certificate).flatMap(ch -> toPractitioner(ch, thumbprint)); + return toJcaX509CertificateHolder(certWrapper.certificate()) + .flatMap(ch -> toPractitioner(ch, certWrapper.thumbprint())); } private Optional toJcaX509CertificateHolder(X509Certificate certificate) diff --git a/dsf-common/dsf-common-auth/src/main/java/dev/dsf/common/auth/conf/Identity.java b/dsf-common/dsf-common-auth/src/main/java/dev/dsf/common/auth/conf/Identity.java index 05dc9484e..61337528b 100644 --- a/dsf-common/dsf-common-auth/src/main/java/dev/dsf/common/auth/conf/Identity.java +++ b/dsf-common/dsf-common-auth/src/main/java/dev/dsf/common/auth/conf/Identity.java @@ -16,7 +16,6 @@ package dev.dsf.common.auth.conf; import java.security.Principal; -import java.security.cert.X509Certificate; import java.util.Optional; import java.util.Set; @@ -44,7 +43,7 @@ public interface Identity extends Principal /** * @return {@link Optional#empty()} if login via OIDC */ - Optional getCertificate(); + Optional getCertificate(); String getDisplayName(); diff --git a/dsf-common/dsf-common-auth/src/main/java/dev/dsf/common/auth/conf/OrganizationIdentityImpl.java b/dsf-common/dsf-common-auth/src/main/java/dev/dsf/common/auth/conf/OrganizationIdentityImpl.java index b348e7ca2..f743078ce 100644 --- a/dsf-common/dsf-common-auth/src/main/java/dev/dsf/common/auth/conf/OrganizationIdentityImpl.java +++ b/dsf-common/dsf-common-auth/src/main/java/dev/dsf/common/auth/conf/OrganizationIdentityImpl.java @@ -15,7 +15,6 @@ */ package dev.dsf.common.auth.conf; -import java.security.cert.X509Certificate; import java.util.Collection; import org.hl7.fhir.r4.model.Endpoint; @@ -37,7 +36,7 @@ public class OrganizationIdentityImpl extends AbstractIdentity implements Organi * may be null */ public OrganizationIdentityImpl(boolean localIdentity, Organization organization, Endpoint endpoint, - Collection dsfRoles, X509Certificate certificate) + Collection dsfRoles, X509CertificateWrapper certificate) { super(localIdentity, organization, endpoint, dsfRoles, certificate); } diff --git a/dsf-common/dsf-common-auth/src/main/java/dev/dsf/common/auth/conf/PractitionerIdentityImpl.java b/dsf-common/dsf-common-auth/src/main/java/dev/dsf/common/auth/conf/PractitionerIdentityImpl.java index 06d8d41bb..c0d1bfdb4 100644 --- a/dsf-common/dsf-common-auth/src/main/java/dev/dsf/common/auth/conf/PractitionerIdentityImpl.java +++ b/dsf-common/dsf-common-auth/src/main/java/dev/dsf/common/auth/conf/PractitionerIdentityImpl.java @@ -15,7 +15,6 @@ */ package dev.dsf.common.auth.conf; -import java.security.cert.X509Certificate; import java.util.Collection; import java.util.Collections; import java.util.HashSet; @@ -55,7 +54,7 @@ public class PractitionerIdentityImpl extends AbstractIdentity implements Practi * may be null */ public PractitionerIdentityImpl(Organization organization, Endpoint endpoint, - Collection dsfRoles, X509Certificate certificate, Practitioner practitioner, + Collection dsfRoles, X509CertificateWrapper certificate, Practitioner practitioner, Collection practitionerRoles, DsfOpenIdCredentials credentials) { super(true, organization, endpoint, dsfRoles, certificate); diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authentication/AbstractProvider.java b/dsf-common/dsf-common-auth/src/main/java/dev/dsf/common/auth/conf/X509CertificateWrapper.java similarity index 54% rename from dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authentication/AbstractProvider.java rename to dsf-common/dsf-common-auth/src/main/java/dev/dsf/common/auth/conf/X509CertificateWrapper.java index 3bec745d6..996f6926e 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authentication/AbstractProvider.java +++ b/dsf-common/dsf-common-auth/src/main/java/dev/dsf/common/auth/conf/X509CertificateWrapper.java @@ -13,44 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package dev.dsf.fhir.authentication; +package dev.dsf.common.auth.conf; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import org.apache.commons.codec.binary.Hex; -import org.hl7.fhir.r4.model.Identifier; -import org.springframework.beans.factory.InitializingBean; +import javax.security.auth.x500.X500Principal; -import dev.dsf.fhir.help.ExceptionHandler; +import org.apache.commons.codec.binary.Hex; -public abstract class AbstractProvider implements InitializingBean +public record X509CertificateWrapper(X509Certificate certificate, String thumbprint, String subjectDn) { - protected final ExceptionHandler exceptionHandler; - - public AbstractProvider(ExceptionHandler exceptionHandler) - { - this.exceptionHandler = exceptionHandler; - } - - @Override - public void afterPropertiesSet() throws Exception + public X509CertificateWrapper(X509Certificate certificate) { - Objects.requireNonNull(exceptionHandler, "exceptionHandler"); + this(certificate, getThumbprint(certificate), getSubjectDn(certificate)); } - protected final Optional getIdentifierValue(List identifiers, String system) - { - return identifiers.stream().filter(Identifier::hasSystem).filter(Identifier::hasValue) - .filter(i -> system.equals(i.getSystem())).map(Identifier::getValue).findFirst(); - } - - protected final String getThumbprint(X509Certificate certificate) + private static String getThumbprint(X509Certificate certificate) { try { @@ -62,4 +43,9 @@ protected final String getThumbprint(X509Certificate certificate) throw new RuntimeException(e); } } + + private static String getSubjectDn(X509Certificate certificate) + { + return certificate.getSubjectX500Principal().getName(X500Principal.RFC1779); + } } diff --git a/dsf-common/dsf-common-auth/src/main/java/dev/dsf/common/auth/logging/CurrentUserMdcLogger.java b/dsf-common/dsf-common-auth/src/main/java/dev/dsf/common/auth/logging/CurrentUserMdcLogger.java index f0d2670c8..ee179acb0 100644 --- a/dsf-common/dsf-common-auth/src/main/java/dev/dsf/common/auth/logging/CurrentUserMdcLogger.java +++ b/dsf-common/dsf-common-auth/src/main/java/dev/dsf/common/auth/logging/CurrentUserMdcLogger.java @@ -15,16 +15,9 @@ */ package dev.dsf.common.auth.logging; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.security.Principal; -import java.security.cert.CertificateEncodingException; -import java.security.cert.X509Certificate; import java.util.stream.Collectors; -import javax.security.auth.x500.X500Principal; - -import org.apache.commons.codec.binary.Hex; import org.slf4j.MDC; import dev.dsf.common.auth.DsfOpenIdCredentials; @@ -32,6 +25,7 @@ import dev.dsf.common.auth.conf.Identity; import dev.dsf.common.auth.conf.OrganizationIdentity; import dev.dsf.common.auth.conf.PractitionerIdentity; +import dev.dsf.common.auth.conf.X509CertificateWrapper; public class CurrentUserMdcLogger extends AbstractUserLogger { @@ -56,8 +50,9 @@ protected void before(OrganizationIdentity organization) { before((Identity) organization); - organization.getCertificate().map(this::getThumbprint).ifPresent(t -> MDC.put(DSF_ORGANIZATION_THUMBPRINT, t)); - organization.getCertificate().map(X509Certificate::getSubjectX500Principal).map(X500Principal::getName) + organization.getCertificate().map(X509CertificateWrapper::thumbprint) + .ifPresent(t -> MDC.put(DSF_ORGANIZATION_THUMBPRINT, t)); + organization.getCertificate().map(X509CertificateWrapper::subjectDn) .ifPresent(d -> MDC.put(DSF_ORGANIZATION_DN, d)); organization.getOrganizationIdentifierValue().ifPresent(i -> MDC.put(DSF_ORGANIZATION_IDENTIFIER, i)); @@ -69,8 +64,9 @@ protected void before(PractitionerIdentity practitioner) { before((Identity) practitioner); - practitioner.getCertificate().map(this::getThumbprint).ifPresent(t -> MDC.put(DSF_PRACTITIONER_THUMBPRINT, t)); - practitioner.getCertificate().map(X509Certificate::getSubjectX500Principal).map(X500Principal::getName) + practitioner.getCertificate().map(X509CertificateWrapper::thumbprint) + .ifPresent(t -> MDC.put(DSF_PRACTITIONER_THUMBPRINT, t)); + practitioner.getCertificate().map(X509CertificateWrapper::subjectDn) .ifPresent(d -> MDC.put(DSF_PRACTITIONER_DN, d)); practitioner.getCredentials().map(DsfOpenIdCredentials::getUserId) .ifPresent(i -> MDC.put(DSF_PRACTITIONER_SUB, i)); @@ -91,19 +87,6 @@ private void before(Identity identity) identity.getDsfRoles().stream().map(DsfRole::name).collect(Collectors.joining(", ", "[", "]"))); } - private String getThumbprint(X509Certificate certificate) - { - try - { - byte[] digest = MessageDigest.getInstance("SHA-512").digest(certificate.getEncoded()); - return Hex.encodeHexString(digest); - } - catch (CertificateEncodingException | NoSuchAlgorithmException e) - { - throw new RuntimeException(e); - } - } - @Override protected void before(Principal principal) { diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authentication/EndpointProvider.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authentication/EndpointProvider.java index 2b7ab6c7a..98cd110b6 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authentication/EndpointProvider.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authentication/EndpointProvider.java @@ -15,7 +15,6 @@ */ package dev.dsf.fhir.authentication; -import java.security.cert.X509Certificate; import java.util.Optional; import org.hl7.fhir.r4.model.Endpoint; @@ -29,5 +28,5 @@ public interface EndpointProvider Optional getLocalEndpointIdentifierValue(); - Optional getEndpoint(Organization organization, X509Certificate x509Certificate); + Optional getEndpoint(Organization organization, String thumbprint); } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authentication/EndpointProviderImpl.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authentication/EndpointProviderImpl.java index 0786071ef..da5b9cff9 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authentication/EndpointProviderImpl.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authentication/EndpointProviderImpl.java @@ -15,13 +15,13 @@ */ package dev.dsf.fhir.authentication; -import java.security.cert.X509Certificate; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.UUID; import org.hl7.fhir.r4.model.Endpoint; +import org.hl7.fhir.r4.model.Identifier; import org.hl7.fhir.r4.model.Organization; import org.hl7.fhir.r4.model.Reference; import org.springframework.beans.factory.InitializingBean; @@ -29,15 +29,15 @@ import dev.dsf.fhir.dao.EndpointDao; import dev.dsf.fhir.help.ExceptionHandler; -public class EndpointProviderImpl extends AbstractProvider implements EndpointProvider, InitializingBean +public class EndpointProviderImpl implements EndpointProvider, InitializingBean { + private final ExceptionHandler exceptionHandler; private final EndpointDao dao; private final String serverBaseUrl; public EndpointProviderImpl(ExceptionHandler exceptionHandler, EndpointDao dao, String serverBaseUrl) { - super(exceptionHandler); - + this.exceptionHandler = exceptionHandler; this.dao = dao; this.serverBaseUrl = serverBaseUrl; } @@ -45,8 +45,7 @@ public EndpointProviderImpl(ExceptionHandler exceptionHandler, EndpointDao dao, @Override public void afterPropertiesSet() throws Exception { - super.afterPropertiesSet(); - + Objects.requireNonNull(exceptionHandler, "exceptionHandler"); Objects.requireNonNull(dao, "dao"); Objects.requireNonNull(serverBaseUrl, "serverBaseUrl"); } @@ -65,11 +64,15 @@ public Optional getLocalEndpointIdentifierValue() .flatMap(ids -> getIdentifierValue(ids, ENDPOINT_IDENTIFIER_SYSTEM)); } - @Override - public Optional getEndpoint(Organization organization, X509Certificate x509Certificate) + private Optional getIdentifierValue(List identifiers, String system) { - String thumbprint = getThumbprint(x509Certificate); + return identifiers.stream().filter(Identifier::hasSystem).filter(Identifier::hasValue) + .filter(i -> system.equals(i.getSystem())).map(Identifier::getValue).findFirst(); + } + @Override + public Optional getEndpoint(Organization organization, String thumbprint) + { Optional endpoint = exceptionHandler.catchAndLogSqlExceptionAndIfReturn( () -> dao.readActiveNotDeletedByThumbprint(thumbprint), Optional::empty); diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authentication/IdentityProviderImpl.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authentication/IdentityProviderImpl.java index 19646208d..9ce3a23b0 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authentication/IdentityProviderImpl.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authentication/IdentityProviderImpl.java @@ -33,6 +33,7 @@ import dev.dsf.common.auth.conf.OrganizationIdentityImpl; import dev.dsf.common.auth.conf.PractitionerIdentityImpl; import dev.dsf.common.auth.conf.RoleConfig; +import dev.dsf.common.auth.conf.X509CertificateWrapper; public class IdentityProviderImpl extends AbstractIdentityProvider implements IdentityProvider, InitializingBean @@ -79,23 +80,24 @@ public Identity getIdentity(X509Certificate[] certificates) if (certificates == null || certificates.length == 0) return null; - String thumbprint = getThumbprint(certificates[0]); + X509CertificateWrapper certWrapper = new X509CertificateWrapper(certificates[0]); - Optional organization = organizationProvider.getOrganization(certificates[0]); + Optional organization = organizationProvider.getOrganization(certWrapper.thumbprint()); if (organization.isPresent()) { Organization o = organization.get(); boolean local = isLocalOrganization(o); - Optional e = local ? getLocalEndpoint() : endpointProvider.getEndpoint(o, certificates[0]); + Optional e = local ? getLocalEndpoint() + : endpointProvider.getEndpoint(o, certWrapper.thumbprint()); Set r = local ? FhirServerRoleImpl.LOCAL_ORGANIZATION : FhirServerRoleImpl.REMOTE_ORGANIZATION; - return new OrganizationIdentityImpl(local, o, e.orElse(null), r, certificates[0]); + return new OrganizationIdentityImpl(local, o, e.orElse(null), r, certWrapper); } - Optional practitioner = toPractitioner(certificates[0]); + Optional practitioner = toPractitioner(certWrapper); Optional localOrganization = getLocalOrganization(); if (practitioner.isPresent() && localOrganization.isPresent()) { @@ -103,14 +105,14 @@ public Identity getIdentity(X509Certificate[] certificates) Organization o = localOrganization.get(); Endpoint e = getLocalEndpoint().orElse(null); - return new PractitionerIdentityImpl(o, e, getDsfRolesFor(p, thumbprint, null, null), certificates[0], p, - getPractitionerRolesFor(p, thumbprint, null, null), null); + return new PractitionerIdentityImpl(o, e, getDsfRolesFor(p, certWrapper.thumbprint(), null, null), + certWrapper, p, getPractitionerRolesFor(p, certWrapper.thumbprint(), null, null), null); } else { logger.warn( "Certificate with thumbprint '{}' for '{}' unknown, not part of allowlist and not configured as local user or local organization", - thumbprint, getDn(certificates[0])); + certWrapper.thumbprint(), certWrapper.subjectDn()); return null; } } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authentication/OrganizationProvider.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authentication/OrganizationProvider.java index ac7e0bece..1c0cfa7d0 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authentication/OrganizationProvider.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authentication/OrganizationProvider.java @@ -15,7 +15,6 @@ */ package dev.dsf.fhir.authentication; -import java.security.cert.X509Certificate; import java.util.Optional; import org.hl7.fhir.r4.model.Organization; @@ -27,12 +26,12 @@ public interface OrganizationProvider String ORGANIZATION_IDENTIFIER_SYSTEM = "http://dsf.dev/sid/organization-identifier"; /** - * @param certificate + * @param thumbprint * may be null - * @return {@link Optional#empty()} if no {@link Organization} is found, or the given {@link X509Certificate} is + * @return {@link Optional#empty()} if no {@link Organization} is found, or the given thumbprint is * null */ - Optional getOrganization(X509Certificate certificate); + Optional getOrganization(String thumbprint); Optional getLocalOrganization(); diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authentication/OrganizationProviderImpl.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authentication/OrganizationProviderImpl.java index f5723ce7b..27af9ad20 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authentication/OrganizationProviderImpl.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authentication/OrganizationProviderImpl.java @@ -15,7 +15,6 @@ */ package dev.dsf.fhir.authentication; -import java.security.cert.X509Certificate; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -28,16 +27,16 @@ import dev.dsf.fhir.dao.OrganizationDao; import dev.dsf.fhir.help.ExceptionHandler; -public class OrganizationProviderImpl extends AbstractProvider implements OrganizationProvider, InitializingBean +public class OrganizationProviderImpl implements OrganizationProvider, InitializingBean { + private final ExceptionHandler exceptionHandler; private final OrganizationDao dao; private final String localOrganizationIdentifierValue; public OrganizationProviderImpl(ExceptionHandler exceptionHandler, OrganizationDao dao, String localOrganizationIdentifierValue) { - super(exceptionHandler); - + this.exceptionHandler = exceptionHandler; this.dao = dao; this.localOrganizationIdentifierValue = localOrganizationIdentifierValue; } @@ -45,20 +44,16 @@ public OrganizationProviderImpl(ExceptionHandler exceptionHandler, OrganizationD @Override public void afterPropertiesSet() throws Exception { - super.afterPropertiesSet(); - Objects.requireNonNull(dao, "dao"); Objects.requireNonNull(localOrganizationIdentifierValue, "localOrganizationIdentifierValue"); } @Override - public Optional getOrganization(X509Certificate certificate) + public Optional getOrganization(String thumbprint) { - if (certificate == null) + if (thumbprint == null) return Optional.empty(); - String thumbprint = getThumbprint(certificate); - return exceptionHandler.catchAndLogSqlExceptionAndIfReturn( () -> dao.readActiveNotDeletedByThumbprint(thumbprint), Optional::empty); } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/exception/DataFormatExceptionHandler.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/exception/DataFormatExceptionHandler.java index 66c7f87f1..61279bd56 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/exception/DataFormatExceptionHandler.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/exception/DataFormatExceptionHandler.java @@ -52,8 +52,9 @@ public void afterPropertiesSet() throws Exception @Override public Response toResponse(DataFormatException exception) { - logger.warn("Error while parsing resource: {}, returning OperationOutcome with status 403 Forbidden", - exception.getMessage()); + logger.warn("Error while parsing resource: {} - {}, returning OperationOutcome with status 403 Forbidden", + exception.getClass().getName(), exception.getMessage()); + logger.debug("Error while parsing resource, returning OperationOutcome with status 403 Forbidden", exception); OperationOutcome outcome = responseGenerator.createOutcome(IssueSeverity.ERROR, IssueType.STRUCTURE, "Unable to parse resource"); diff --git a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/authentication/IdentityProviderTest.java b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/authentication/IdentityProviderTest.java index 8855fd759..5bb709afe 100644 --- a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/authentication/IdentityProviderTest.java +++ b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/authentication/IdentityProviderTest.java @@ -27,7 +27,7 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.security.cert.CertificateException; +import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.time.Period; import java.util.List; @@ -54,6 +54,7 @@ import dev.dsf.common.auth.conf.PractitionerIdentity; import dev.dsf.common.auth.conf.RoleConfig; import dev.dsf.common.auth.conf.RoleConfig.Mapping; +import dev.dsf.common.auth.conf.X509CertificateWrapper; import dev.dsf.fhir.authentication.FhirServerRoleImpl.Operation; public class IdentityProviderTest @@ -84,8 +85,11 @@ public class IdentityProviderTest private static final Organization LOCAL_ORGANIZATION = new Organization(); private static final X509Certificate LOCAL_ORGANIZATION_CERTIFICATE; + private static final String LOCAL_ORGANIZATION_CERTIFICATE_THUMBPRINT; + private static final Organization REMOTE_ORGANIZATION = new Organization(); private static final X509Certificate REMOTE_ORGANIZATION_CERTIFICATE; + private static final String REMOTE_ORGANIZATION_CERTIFICATE_THUMBPRINT; private static final Endpoint LOCAL_ENDPOINT = new Endpoint(); private static final Endpoint REMOTE_ENDPOINT = new Endpoint(); @@ -94,40 +98,43 @@ public class IdentityProviderTest private static final String LOCAL_PRACTITIONER_CERTIFICATE_THUMBPRINT; static + { + CA = CertificateAuthority.builderSha384EcdsaSecp384r1("DE", null, null, null, null, "CA") + .setValidityPeriod(Period.ofDays(1)).build(); + + CertificationRequest localOrgReq = CertificationRequest + .builder(CA, "DE", null, null, null, null, LOCAL_ORGANIZATION_COMMON_NAME).generateKeyPair() + .setEmail("email@local.org").build(); + LOCAL_ORGANIZATION_CERTIFICATE = CA.signClientCertificate(localOrgReq); + LOCAL_ORGANIZATION_CERTIFICATE_THUMBPRINT = toThumbprint(LOCAL_ORGANIZATION_CERTIFICATE); + LOCAL_ORGANIZATION.addIdentifier().setSystem(OrganizationProvider.ORGANIZATION_IDENTIFIER_SYSTEM) + .setValue(LOCAL_ORGANIZATION_IDENTIFIER_VALUE); + + CertificationRequest remoteOrgReq = CertificationRequest + .builder(CA, "DE", null, null, null, null, REMOTE_ORGANIZATION_COMMON_NAME).generateKeyPair() + .setEmail("email@remote.org").build(); + REMOTE_ORGANIZATION_CERTIFICATE = CA.signClientCertificate(remoteOrgReq); + REMOTE_ORGANIZATION_CERTIFICATE_THUMBPRINT = toThumbprint(REMOTE_ORGANIZATION_CERTIFICATE); + REMOTE_ORGANIZATION.addIdentifier().setSystem(OrganizationProvider.ORGANIZATION_IDENTIFIER_SYSTEM) + .setValue(REMOTE_ORGANIZATION_IDENTIFIER_VALUE); + + CertificationRequest localPractitionerReq = CertificationRequest + .builder(CA, "DE", null, null, null, null, LOCAL_PRACTITIONER_COMMON_NAME).generateKeyPair() + .setEmail(LOCAL_PRACTITIONER_MAIL).build(); + LOCAL_PRACTITIONER_CERTIFICATE = CA.signClientCertificate(localPractitionerReq); + LOCAL_PRACTITIONER_CERTIFICATE_THUMBPRINT = toThumbprint(LOCAL_PRACTITIONER_CERTIFICATE); + } + + private static String toThumbprint(X509Certificate certificate) { try { - CA = CertificateAuthority.builderSha384EcdsaSecp384r1("DE", null, null, null, null, "CA") - .setValidityPeriod(Period.ofDays(1)).build(); - - CertificationRequest localOrgReq = CertificationRequest - .builder(CA, "DE", null, null, null, null, LOCAL_ORGANIZATION_COMMON_NAME).generateKeyPair() - .setEmail("email@local.org").build(); - LOCAL_ORGANIZATION_CERTIFICATE = CA.signClientCertificate(localOrgReq); - - CertificationRequest remoteOrgReq = CertificationRequest - .builder(CA, "DE", null, null, null, null, REMOTE_ORGANIZATION_COMMON_NAME).generateKeyPair() - .setEmail("email@remote.org").build(); - REMOTE_ORGANIZATION_CERTIFICATE = CA.signClientCertificate(remoteOrgReq); - - CertificationRequest localPractitionerReq = CertificationRequest - .builder(CA, "DE", null, null, null, null, LOCAL_PRACTITIONER_COMMON_NAME).generateKeyPair() - .setEmail(LOCAL_PRACTITIONER_MAIL).build(); - LOCAL_PRACTITIONER_CERTIFICATE = CA.signClientCertificate(localPractitionerReq); - - LOCAL_PRACTITIONER_CERTIFICATE_THUMBPRINT = Hex.encodeHexString( - MessageDigest.getInstance("SHA-512").digest(LOCAL_PRACTITIONER_CERTIFICATE.getEncoded())); + return Hex.encodeHexString(MessageDigest.getInstance("SHA-512").digest(certificate.getEncoded())); } - catch (NoSuchAlgorithmException | CertificateException e) + catch (CertificateEncodingException | NoSuchAlgorithmException e) { throw new RuntimeException(e); } - - LOCAL_ORGANIZATION.addIdentifier().setSystem(OrganizationProvider.ORGANIZATION_IDENTIFIER_SYSTEM) - .setValue(LOCAL_ORGANIZATION_IDENTIFIER_VALUE); - - REMOTE_ORGANIZATION.addIdentifier().setSystem(OrganizationProvider.ORGANIZATION_IDENTIFIER_SYSTEM) - .setValue(REMOTE_ORGANIZATION_IDENTIFIER_VALUE); } private OrganizationProvider organizationProvider; @@ -199,7 +206,7 @@ public void testGetOrganizationIdentityByX509CertificateLocalOrganization() thro { IdentityProvider provider = createIdentityProvider(List.of()); - when(organizationProvider.getOrganization(LOCAL_ORGANIZATION_CERTIFICATE)) + when(organizationProvider.getOrganization(LOCAL_ORGANIZATION_CERTIFICATE_THUMBPRINT)) .thenReturn(Optional.of(LOCAL_ORGANIZATION)); when(endpointProvider.getLocalEndpoint()).thenReturn(Optional.of(LOCAL_ENDPOINT)); @@ -210,18 +217,19 @@ public void testGetOrganizationIdentityByX509CertificateLocalOrganization() thro OrganizationIdentity orgI = (OrganizationIdentity) i; assertNotNull(orgI.getCertificate()); assertTrue(orgI.getCertificate().isPresent()); - assertEquals(LOCAL_ORGANIZATION_CERTIFICATE, orgI.getCertificate().get()); + assertEquals(LOCAL_ORGANIZATION_CERTIFICATE, + orgI.getCertificate().map(X509CertificateWrapper::certificate).get()); assertEquals(LOCAL_ORGANIZATION_IDENTIFIER_VALUE, orgI.getDisplayName()); assertEquals(FhirServerRoleImpl.LOCAL_ORGANIZATION, orgI.getDsfRoles()); assertEquals(LOCAL_ORGANIZATION_IDENTIFIER_VALUE, orgI.getName()); assertEquals(LOCAL_ORGANIZATION, orgI.getOrganization()); assertEquals(LOCAL_ORGANIZATION_IDENTIFIER_VALUE, orgI.getOrganizationIdentifierValue().get()); - ArgumentCaptor cArg1 = ArgumentCaptor.forClass(X509Certificate.class); + ArgumentCaptor cArg1 = ArgumentCaptor.forClass(String.class); verify(organizationProvider).getOrganization(cArg1.capture()); verify(endpointProvider).getLocalEndpoint(); - assertEquals(LOCAL_ORGANIZATION_CERTIFICATE, cArg1.getValue()); + assertEquals(LOCAL_ORGANIZATION_CERTIFICATE_THUMBPRINT, cArg1.getValue()); } @Test @@ -229,9 +237,9 @@ public void testGetOrganizationIdentityByX509CertificateRemoteOrganization() thr { IdentityProvider provider = createIdentityProvider(List.of()); - when(organizationProvider.getOrganization(REMOTE_ORGANIZATION_CERTIFICATE)) + when(organizationProvider.getOrganization(REMOTE_ORGANIZATION_CERTIFICATE_THUMBPRINT)) .thenReturn(Optional.of(REMOTE_ORGANIZATION)); - when(endpointProvider.getEndpoint(REMOTE_ORGANIZATION, REMOTE_ORGANIZATION_CERTIFICATE)) + when(endpointProvider.getEndpoint(REMOTE_ORGANIZATION, REMOTE_ORGANIZATION_CERTIFICATE_THUMBPRINT)) .thenReturn(Optional.of(REMOTE_ENDPOINT)); Identity i = provider.getIdentity(new X509Certificate[] { REMOTE_ORGANIZATION_CERTIFICATE }); @@ -241,24 +249,25 @@ public void testGetOrganizationIdentityByX509CertificateRemoteOrganization() thr OrganizationIdentity orgI = (OrganizationIdentity) i; assertNotNull(orgI.getCertificate()); assertTrue(orgI.getCertificate().isPresent()); - assertEquals(REMOTE_ORGANIZATION_CERTIFICATE, orgI.getCertificate().get()); + assertEquals(REMOTE_ORGANIZATION_CERTIFICATE, + orgI.getCertificate().map(X509CertificateWrapper::certificate).get()); assertEquals(REMOTE_ORGANIZATION_IDENTIFIER_VALUE, orgI.getDisplayName()); assertEquals(FhirServerRoleImpl.REMOTE_ORGANIZATION, orgI.getDsfRoles()); assertEquals(REMOTE_ORGANIZATION_IDENTIFIER_VALUE, orgI.getName()); assertEquals(REMOTE_ORGANIZATION, orgI.getOrganization()); assertEquals(REMOTE_ORGANIZATION_IDENTIFIER_VALUE, orgI.getOrganizationIdentifierValue().get()); - ArgumentCaptor getOrgArg1 = ArgumentCaptor.forClass(X509Certificate.class); + ArgumentCaptor getOrgArg1 = ArgumentCaptor.forClass(String.class); verify(organizationProvider).getOrganization(getOrgArg1.capture()); - assertEquals(REMOTE_ORGANIZATION_CERTIFICATE, getOrgArg1.getValue()); + assertEquals(REMOTE_ORGANIZATION_CERTIFICATE_THUMBPRINT, getOrgArg1.getValue()); ArgumentCaptor getEndpArg1 = ArgumentCaptor.forClass(Organization.class); - ArgumentCaptor getEndpArg2 = ArgumentCaptor.forClass(X509Certificate.class); + ArgumentCaptor getEndpArg2 = ArgumentCaptor.forClass(String.class); verify(endpointProvider).getEndpoint(getEndpArg1.capture(), getEndpArg2.capture()); assertEquals(REMOTE_ORGANIZATION, getEndpArg1.getValue()); - assertEquals(REMOTE_ORGANIZATION_CERTIFICATE, getEndpArg2.getValue()); + assertEquals(REMOTE_ORGANIZATION_CERTIFICATE_THUMBPRINT, getEndpArg2.getValue()); } @Test @@ -266,17 +275,18 @@ public void testGetOrganizationIdentityByX509CertificateUnknownOrganization() th { IdentityProvider provider = createIdentityProvider(List.of()); - when(organizationProvider.getOrganization(REMOTE_ORGANIZATION_CERTIFICATE)).thenReturn(Optional.empty()); + when(organizationProvider.getOrganization(REMOTE_ORGANIZATION_CERTIFICATE_THUMBPRINT)) + .thenReturn(Optional.empty()); when(organizationProvider.getLocalOrganization()).thenReturn(Optional.of(LOCAL_ORGANIZATION)); Identity i = provider.getIdentity(new X509Certificate[] { REMOTE_ORGANIZATION_CERTIFICATE }); assertNull(i); - ArgumentCaptor cArg1 = ArgumentCaptor.forClass(X509Certificate.class); + ArgumentCaptor cArg1 = ArgumentCaptor.forClass(String.class); verify(organizationProvider).getOrganization(cArg1.capture()); verify(organizationProvider).getLocalOrganization(); - assertEquals(REMOTE_ORGANIZATION_CERTIFICATE, cArg1.getValue()); + assertEquals(REMOTE_ORGANIZATION_CERTIFICATE_THUMBPRINT, cArg1.getValue()); } @Test @@ -285,7 +295,8 @@ public void testGetPractitionerIdentityByX509Certificate() throws Exception IdentityProvider provider = createIdentityProvider( List.of(createMappingWithThumbprint(LOCAL_PRACTITIONER_CERTIFICATE_THUMBPRINT))); - when(organizationProvider.getOrganization(LOCAL_ORGANIZATION_CERTIFICATE)).thenReturn(Optional.empty()); + when(organizationProvider.getOrganization(LOCAL_ORGANIZATION_CERTIFICATE_THUMBPRINT)) + .thenReturn(Optional.empty()); when(organizationProvider.getLocalOrganization()).thenReturn(Optional.of(LOCAL_ORGANIZATION)); when(endpointProvider.getLocalEndpoint()).thenReturn(Optional.of(LOCAL_ENDPOINT)); when(roleConfig.getDsfRolesForEmail(LOCAL_PRACTITIONER_MAIL)) @@ -303,7 +314,8 @@ public void testGetPractitionerIdentityByX509Certificate() throws Exception PractitionerIdentity practitionerI = (PractitionerIdentity) i; assertNotNull(practitionerI.getCertificate()); assertTrue(practitionerI.getCertificate().isPresent()); - assertEquals(LOCAL_PRACTITIONER_CERTIFICATE, practitionerI.getCertificate().get()); + assertEquals(LOCAL_PRACTITIONER_CERTIFICATE, + practitionerI.getCertificate().map(X509CertificateWrapper::certificate).get()); assertNotNull(practitionerI.getCredentials()); assertTrue(practitionerI.getCredentials().isEmpty()); assertEquals(LOCAL_PRACTITIONER_NAME_GIVEN + " " + LOCAL_PRACTITIONER_NAME_FAMILY, @@ -316,7 +328,7 @@ public void testGetPractitionerIdentityByX509Certificate() throws Exception assertEquals(Set.of(PRACTIONER_ROLE1, PRACTIONER_ROLE2), practitionerI.getPractionerRoles()); assertNotNull(practitionerI.getPractitioner()); - ArgumentCaptor cArg1 = ArgumentCaptor.forClass(X509Certificate.class); + ArgumentCaptor cArg1 = ArgumentCaptor.forClass(String.class); verify(organizationProvider).getOrganization(cArg1.capture()); verify(organizationProvider).getLocalOrganization(); verify(endpointProvider).getLocalEndpoint(); @@ -330,7 +342,7 @@ public void testGetPractitionerIdentityByX509Certificate() throws Exception ArgumentCaptor tArg2 = ArgumentCaptor.forClass(String.class); verify(roleConfig).getPractitionerRolesForThumbprint(tArg2.capture()); - assertEquals(LOCAL_PRACTITIONER_CERTIFICATE, cArg1.getValue()); + assertEquals(LOCAL_PRACTITIONER_CERTIFICATE_THUMBPRINT, cArg1.getValue()); assertEquals(LOCAL_PRACTITIONER_MAIL, mArg1.getValue()); assertEquals(LOCAL_PRACTITIONER_MAIL, mArg2.getValue()); assertEquals(LOCAL_PRACTITIONER_CERTIFICATE_THUMBPRINT, tArg1.getValue()); @@ -343,17 +355,18 @@ public void testGetPractitionerIdentityByX509CertificateNoLocalOrganization() th IdentityProvider provider = createIdentityProvider( List.of(createMappingWithThumbprint(LOCAL_PRACTITIONER_CERTIFICATE_THUMBPRINT))); - when(organizationProvider.getOrganization(LOCAL_ORGANIZATION_CERTIFICATE)).thenReturn(Optional.empty()); + when(organizationProvider.getOrganization(LOCAL_ORGANIZATION_CERTIFICATE_THUMBPRINT)) + .thenReturn(Optional.empty()); when(organizationProvider.getLocalOrganization()).thenReturn(Optional.empty()); Identity i = provider.getIdentity(new X509Certificate[] { LOCAL_PRACTITIONER_CERTIFICATE }); assertNull(i); - ArgumentCaptor cArg1 = ArgumentCaptor.forClass(X509Certificate.class); + ArgumentCaptor cArg1 = ArgumentCaptor.forClass(String.class); verify(organizationProvider).getOrganization(cArg1.capture()); verify(organizationProvider).getLocalOrganization(); - assertEquals(LOCAL_PRACTITIONER_CERTIFICATE, cArg1.getValue()); + assertEquals(LOCAL_PRACTITIONER_CERTIFICATE_THUMBPRINT, cArg1.getValue()); } @Test diff --git a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/authorization/process/TestOrganizationIdentity.java b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/authorization/process/TestOrganizationIdentity.java index c1dda6b37..0c3d7e615 100644 --- a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/authorization/process/TestOrganizationIdentity.java +++ b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/authorization/process/TestOrganizationIdentity.java @@ -15,7 +15,6 @@ */ package dev.dsf.fhir.authorization.process; -import java.security.cert.X509Certificate; import java.util.Optional; import java.util.Set; @@ -24,6 +23,7 @@ import dev.dsf.common.auth.conf.DsfRole; import dev.dsf.common.auth.conf.OrganizationIdentity; +import dev.dsf.common.auth.conf.X509CertificateWrapper; public class TestOrganizationIdentity implements OrganizationIdentity { @@ -90,7 +90,7 @@ public boolean hasDsfRole(DsfRole role) } @Override - public Optional getCertificate() + public Optional getCertificate() { throw new UnsupportedOperationException(); } diff --git a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/authorization/process/TestPractitionerIdentity.java b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/authorization/process/TestPractitionerIdentity.java index 897492ba9..6ace10d12 100644 --- a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/authorization/process/TestPractitionerIdentity.java +++ b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/authorization/process/TestPractitionerIdentity.java @@ -15,7 +15,6 @@ */ package dev.dsf.fhir.authorization.process; -import java.security.cert.X509Certificate; import java.util.Collection; import java.util.HashSet; import java.util.List; @@ -30,6 +29,7 @@ import dev.dsf.common.auth.DsfOpenIdCredentials; import dev.dsf.common.auth.conf.DsfRole; import dev.dsf.common.auth.conf.PractitionerIdentity; +import dev.dsf.common.auth.conf.X509CertificateWrapper; public class TestPractitionerIdentity implements PractitionerIdentity { @@ -92,7 +92,7 @@ public boolean hasDsfRole(DsfRole role) } @Override - public Optional getCertificate() + public Optional getCertificate() { throw new UnsupportedOperationException();