Skip to content
Snippets Groups Projects
Commit 694b17c0 authored by Evgeny Bardin's avatar Evgeny Bardin
Browse files

Merge branch 'OZG-7092-Refactor-Interface' into 'main'

OZG-7092 Anpassung TokenChecker

See merge request !1
parents 31dd9e10 ade8a8ef
No related branches found
No related tags found
1 merge request!1OZG-7092 Anpassung TokenChecker
Showing
with 382 additions and 425 deletions
...@@ -78,7 +78,6 @@ pipeline { ...@@ -78,7 +78,6 @@ pipeline {
} }
configFileProvider([configFile(fileId: 'maven-settings', variable: 'MAVEN_SETTINGS')]) { configFileProvider([configFile(fileId: 'maven-settings', variable: 'MAVEN_SETTINGS')]) {
sh 'mvn -s $MAVEN_SETTINGS -DskipTests deploy -Dmaven.wagon.http.retryHandler.count=3' sh 'mvn -s $MAVEN_SETTINGS -DskipTests deploy -Dmaven.wagon.http.retryHandler.count=3'
sh "mvn -s $MAVEN_SETTINGS versions:revert"
} }
} }
} }
...@@ -90,6 +89,7 @@ pipeline { ...@@ -90,6 +89,7 @@ pipeline {
configFileProvider([configFile(fileId: 'maven-settings', variable: 'MAVEN_SETTINGS')]) { configFileProvider([configFile(fileId: 'maven-settings', variable: 'MAVEN_SETTINGS')]) {
dir('token-checker-server'){ dir('token-checker-server'){
sh 'mvn --no-transfer-progress -s $MAVEN_SETTINGS spring-boot:build-image -DskipTests -Dmaven.wagon.http.retryHandler.count=3' sh 'mvn --no-transfer-progress -s $MAVEN_SETTINGS spring-boot:build-image -DskipTests -Dmaven.wagon.http.retryHandler.count=3'
sh "mvn -s $MAVEN_SETTINGS versions:revert"
} }
} }
......
...@@ -20,4 +20,4 @@ Zentraler Dienst um (Saml-)Token auf Gültigkeit zu prüfen. Unterstützt werden ...@@ -20,4 +20,4 @@ Zentraler Dienst um (Saml-)Token auf Gültigkeit zu prüfen. Unterstützt werden
* name: String * name: String
* value: String * value: String
* checkError * checkError
* message: String * checkErrors: GrpcError
\ No newline at end of file
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
<parent> <parent>
<groupId>de.ozgcloud.common</groupId> <groupId>de.ozgcloud.common</groupId>
<artifactId>ozgcloud-common-dependencies</artifactId> <artifactId>ozgcloud-common-dependencies</artifactId>
<version>4.3.1</version> <version>4.7.0</version>
<relativePath/> <relativePath/>
</parent> </parent>
......
...@@ -6,17 +6,33 @@ option java_multiple_files = true; ...@@ -6,17 +6,33 @@ option java_multiple_files = true;
option java_package = "de.ozgcloud.token"; option java_package = "de.ozgcloud.token";
option java_outer_classname = "TokenCheckModelProto"; option java_outer_classname = "TokenCheckModelProto";
message GrpcTokenAttribute { message GrpcCheckTokenRequest {
string token = 1;
}
message GrpcCheckTokenResponse {
bool tokenValid = 1;
oneof checkTokenResult {
GrpcTokenAttributes tokenAttributes = 2;
GrpcCheckErrors checkErrors = 3;
}
}
message GrpcTokenAttributes {
string postfachId = 1;
string trustLevel = 2;
repeated GrpcOtherField otherFields = 3;
}
message GrpcOtherField {
string name = 1; string name = 1;
string value = 2; string value = 2;
} }
message GrpcTokenCheckRequest { message GrpcCheckErrors {
string token = 1; repeated GrpcCheckError checkError = 1;
} }
message GrpcTokenCheckResponse { message GrpcCheckError {
string postkorbHandle = 1; string message = 1;
string trustLevel = 2;
repeated GrpcTokenAttribute otherFields = 3;
} }
...@@ -9,6 +9,6 @@ option java_package = "de.ozgcloud.token"; ...@@ -9,6 +9,6 @@ option java_package = "de.ozgcloud.token";
option java_outer_classname = "TokenCheckProto"; option java_outer_classname = "TokenCheckProto";
service TokenCheckService { service TokenCheckService {
rpc CheckToken(GrpcTokenCheckRequest) returns (GrpcTokenCheckResponse) { rpc CheckToken(GrpcCheckTokenRequest) returns (GrpcCheckTokenResponse) {
} }
} }
\ No newline at end of file
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
<parent> <parent>
<groupId>de.ozgcloud.common</groupId> <groupId>de.ozgcloud.common</groupId>
<artifactId>ozgcloud-common-parent</artifactId> <artifactId>ozgcloud-common-parent</artifactId>
<version>4.5.0</version> <version>4.7.0</version>
<relativePath/> <relativePath/>
</parent> </parent>
...@@ -64,7 +64,7 @@ ...@@ -64,7 +64,7 @@
<dependency> <dependency>
<groupId>de.ozgcloud.token</groupId> <groupId>de.ozgcloud.token</groupId>
<artifactId>token-checker-interface</artifactId> <artifactId>token-checker-interface</artifactId>
<version>0.1.0-SNAPSHOT</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency> <dependency>
......
#
# Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
# Ministerpräsidenten des Landes Schleswig-Holstein
# Staatskanzlei
# Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
#
# Lizenziert unter der EUPL, Version 1.2 oder - sobald
# diese von der Europäischen Kommission genehmigt wurden -
# Folgeversionen der EUPL ("Lizenz");
# Sie dürfen dieses Werk ausschließlich gemäß
# dieser Lizenz nutzen.
# Eine Kopie der Lizenz finden Sie hier:
#
# https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
#
# Sofern nicht durch anwendbare Rechtsvorschriften
# gefordert oder in schriftlicher Form vereinbart, wird
# die unter der Lizenz verbreitete Software "so wie sie
# ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
# ausdrücklich oder stillschweigend - verbreitet.
# Die sprachspezifischen Genehmigungen und Beschränkungen
# unter der Lizenz sind dem Lizenztext zu entnehmen.
#
apiVersion: v1
kind: ConfigMap
metadata:
name: token-checker-server-bindings-type
namespace: {{ include "app.namespace" . }}
data:
type: |
ca-certificates
\ No newline at end of file
...@@ -59,8 +59,6 @@ spec: ...@@ -59,8 +59,6 @@ spec:
app.kubernetes.io/name: {{ .Release.Name }} app.kubernetes.io/name: {{ .Release.Name }}
containers: containers:
- env: - env:
- name: SERVICE_BINDING_ROOT
value: "/bindings"
- name: spring_profiles_active - name: spring_profiles_active
value: {{ include "app.envSpringProfiles" . }} value: {{ include "app.envSpringProfiles" . }}
...@@ -72,10 +70,14 @@ spec: ...@@ -72,10 +70,14 @@ spec:
value: file:///keystore/enc.crt value: file:///keystore/enc.crt
- name: OZGCLOUD_TOKEN_CHECK_ENTITIES_0_METADATA - name: OZGCLOUD_TOKEN_CHECK_ENTITIES_0_METADATA
value: file:///metadata/muk-idp-infra.xml value: file:///metadata/muk-idp-infra.xml
- name: OZGCLOUD_TOKEN_CHECK_ENTITIES_0_USE-ID-AS-POSTKORB-HANDLE - name: OZGCLOUD_TOKEN_CHECK_ENTITIES_0_USE-ID-AS-POSTFACH-ID
value: {{ quote (index ((.Values.ozgcloud).tokenChecker).entities 0).useIdAsPostkorbHandle | default "\"true\""}} value: {{ quote (index ((.Values.ozgcloud).tokenChecker).entities 0).useIdAsPostfachId | default "\"true\""}}
- name: OZGCLOUD_TOKEN_CHECK_ENTITIES_0_MAPPINGS_TRUST-LEVEL - name: OZGCLOUD_TOKEN_CHECK_ENTITIES_0_MAPPINGS_TRUST-LEVEL
value: {{ required "at least one ozgcloud.token.check.entities.mappings trustlevel must be set" (index ((.Values.ozgcloud).tokenChecker).entities 0).mappings.trustLevel }} value: {{ required "at least one ozgcloud.token.check.entities.mappings trustlevel must be set" (index ((.Values.ozgcloud).tokenChecker).entities 0).mappings.trustLevel }}
{{- if eq (index ((.Values.ozgcloud).tokenChecker).entities 0).useIdAsPostfachId false }}
- name: OZGCLOUD_TOKEN_CHECK_ENTITIES_0_MAPPINGS_POSTFACH-ID
value: {{ required "at least one ozgcloud.token.check.entities.mappings postfachId must be set" (index ((.Values.ozgcloud).tokenChecker).entities 0).mappings.postfachId }}
{{- end }}
{{- with include "app.getCustomList" . }} {{- with include "app.getCustomList" . }}
{{ . | indent 10 }} {{ . | indent 10 }}
...@@ -134,10 +136,6 @@ spec: ...@@ -134,10 +136,6 @@ spec:
terminationMessagePolicy: File terminationMessagePolicy: File
tty: true tty: true
volumeMounts: volumeMounts:
- name: bindings
mountPath: "/bindings/ca-certificates/type"
subPath: type
readOnly: true
- name: saml-mount - name: saml-mount
mountPath: "/keystore/enc.crt" mountPath: "/keystore/enc.crt"
subPath: enc.crt subPath: enc.crt
...@@ -152,9 +150,6 @@ spec: ...@@ -152,9 +150,6 @@ spec:
readOnly: true readOnly: true
volumes: volumes:
- name: bindings
configMap:
name: token-checker-server-bindings-type
- name: saml-mount - name: saml-mount
secret: secret:
secretName: {{ required ".Values.samlRegistrationSecretName must be set" .Values.samlRegistrationSecretName }} secretName: {{ required ".Values.samlRegistrationSecretName must be set" .Values.samlRegistrationSecretName }}
......
{{- if (.Values.serviceAccount).create }}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "app.serviceAccountName" . }}
namespace: {{ include "app.namespace" . }}
{{- end }}
\ No newline at end of file
/*
* Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
* Ministerpräsidenten des Landes Schleswig-Holstein
* Staatskanzlei
* Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
*
* Lizenziert unter der EUPL, Version 1.2 oder - sobald
* diese von der Europäischen Kommission genehmigt wurden -
* Folgeversionen der EUPL ("Lizenz");
* Sie dürfen dieses Werk ausschließlich gemäß
* dieser Lizenz nutzen.
* Eine Kopie der Lizenz finden Sie hier:
*
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
*
* Sofern nicht durch anwendbare Rechtsvorschriften
* gefordert oder in schriftlicher Form vereinbart, wird
* die unter der Lizenz verbreitete Software "so wie sie
* ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
* ausdrücklich oder stillschweigend - verbreitet.
* Die sprachspezifischen Genehmigungen und Beschränkungen
* unter der Lizenz sind dem Lizenztext zu entnehmen.
*/
package de.ozgcloud.token;
import io.grpc.stub.StreamObserver;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import net.devh.boot.grpc.server.service.GrpcService;
@Log4j2
@GrpcService
@RequiredArgsConstructor
public class GrpcTokenCheckService extends TokenCheckServiceGrpc.TokenCheckServiceImplBase {
private final TokenCheckService tokenCheckerService;
private final TokenCheckMapper mapper;
@Override
public void checkToken(GrpcTokenCheckRequest request, StreamObserver<GrpcTokenCheckResponse> responseStreamObserver) {
try {
var result = tokenCheckerService.checkToken(request.getToken());
responseStreamObserver.onNext(mapper.toGrpcTokenResponse(result));
responseStreamObserver.onCompleted();
} catch (Exception e) {
LOG.error("Error checking token.", e);
responseStreamObserver.onError(e);
}
}
}
...@@ -24,7 +24,14 @@ ...@@ -24,7 +24,14 @@
package de.ozgcloud.token; package de.ozgcloud.token;
import lombok.Builder; import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
@Builder @Builder
public record TokenAttribute(String name, String value) { @Getter
public class TokenAttribute {
private final String name;
private final String value;
} }
\ No newline at end of file
...@@ -25,17 +25,20 @@ package de.ozgcloud.token; ...@@ -25,17 +25,20 @@ package de.ozgcloud.token;
import java.util.List; import java.util.List;
import org.springframework.security.saml2.core.Saml2Error; import lombok.Builder;
import de.ozgcloud.common.errorhandling.TechnicalException;
import lombok.Getter; import lombok.Getter;
import lombok.Singular;
@Builder
@Getter @Getter
public class TokenVerificationException extends TechnicalException { public class TokenAttributes {
private final List<Saml2Error> errorList;
public static final String POSTFACH_ID_KEY = "postfachId";
public static final String TRUST_LEVEL_KEY = "trustLevel";
private final String postfachId;
private final String trustLevel;
@Singular
private final List<TokenAttribute> otherAttributes;
public TokenVerificationException(final String msg, final List<Saml2Error> errorList) {
super(msg);
this.errorList = errorList;
}
} }
...@@ -23,25 +23,16 @@ ...@@ -23,25 +23,16 @@
*/ */
package de.ozgcloud.token; package de.ozgcloud.token;
import java.util.HashMap;
import java.util.TimeZone; import java.util.TimeZone;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan; import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.context.annotation.EnableAspectJAutoProxy;
import de.ozgcloud.token.saml.SamlTokenUtils;
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
import net.shibboleth.utilities.java.support.xml.BasicParserPool;
import net.shibboleth.utilities.java.support.xml.ParserPool;
@SpringBootApplication(scanBasePackages = { "de.ozgcloud" }) @SpringBootApplication(scanBasePackages = { "de.ozgcloud" })
@ConfigurationPropertiesScan("de.ozgcloud.token") @ConfigurationPropertiesScan("de.ozgcloud.token")
@EnableAspectJAutoProxy(proxyTargetClass = true) @EnableAspectJAutoProxy(proxyTargetClass = true)
@EnableConfigurationProperties(TokenCheckProperties.class)
public class TokenCheckApplication { public class TokenCheckApplication {
public static void main(String[] args) { public static void main(String[] args) {
...@@ -49,15 +40,4 @@ public class TokenCheckApplication { ...@@ -49,15 +40,4 @@ public class TokenCheckApplication {
SpringApplication.run(TokenCheckApplication.class, args); SpringApplication.run(TokenCheckApplication.class, args);
} }
@Bean
public ParserPool parserPool() throws ComponentInitializationException {
var localParserPool = new BasicParserPool();
final var features = SamlTokenUtils.createFeatureMap();
localParserPool.setBuilderFeatures(features);
localParserPool.setBuilderAttributes(new HashMap<>());
localParserPool.initialize();
return localParserPool;
}
} }
/*
* Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
* Ministerpräsidenten des Landes Schleswig-Holstein
* Staatskanzlei
* Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
*
* Lizenziert unter der EUPL, Version 1.2 oder - sobald
* diese von der Europäischen Kommission genehmigt wurden -
* Folgeversionen der EUPL ("Lizenz");
* Sie dürfen dieses Werk ausschließlich gemäß
* dieser Lizenz nutzen.
* Eine Kopie der Lizenz finden Sie hier:
*
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
*
* Sofern nicht durch anwendbare Rechtsvorschriften
* gefordert oder in schriftlicher Form vereinbart, wird
* die unter der Lizenz verbreitete Software "so wie sie
* ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
* ausdrücklich oder stillschweigend - verbreitet.
* Die sprachspezifischen Genehmigungen und Beschränkungen
* unter der Lizenz sind dem Lizenztext zu entnehmen.
*/
package de.ozgcloud.token;
import static de.ozgcloud.token.saml.SamlTokenUtils.*;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import jakarta.annotation.PostConstruct;
import org.opensaml.core.config.ConfigurationService;
import org.opensaml.core.config.InitializationService;
import org.opensaml.core.criterion.EntityIdCriterion;
import org.opensaml.core.xml.XMLObject;
import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
import org.opensaml.saml.criterion.ProtocolCriterion;
import org.opensaml.saml.metadata.criteria.role.impl.EvaluableProtocolRoleDescriptorCriterion;
import org.opensaml.saml.saml2.encryption.Decrypter;
import org.opensaml.saml.saml2.encryption.EncryptedElementTypeEncryptedKeyResolver;
import org.opensaml.security.credential.Credential;
import org.opensaml.security.credential.CredentialResolver;
import org.opensaml.security.credential.CredentialSupport;
import org.opensaml.security.credential.UsageType;
import org.opensaml.security.credential.criteria.impl.EvaluableEntityIDCredentialCriterion;
import org.opensaml.security.credential.criteria.impl.EvaluableUsageCredentialCriterion;
import org.opensaml.security.credential.impl.CollectionCredentialResolver;
import org.opensaml.security.criteria.UsageCriterion;
import org.opensaml.security.x509.BasicX509Credential;
import org.opensaml.xmlsec.config.impl.DefaultSecurityConfigurationBootstrap;
import org.opensaml.xmlsec.encryption.support.ChainingEncryptedKeyResolver;
import org.opensaml.xmlsec.encryption.support.EncryptedKeyResolver;
import org.opensaml.xmlsec.encryption.support.InlineEncryptedKeyResolver;
import org.opensaml.xmlsec.encryption.support.SimpleRetrievalMethodEncryptedKeyResolver;
import org.opensaml.xmlsec.keyinfo.impl.CollectionKeyInfoCredentialResolver;
import org.opensaml.xmlsec.signature.support.SignatureTrustEngine;
import org.opensaml.xmlsec.signature.support.impl.ExplicitKeySignatureTrustEngine;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.security.saml2.Saml2Exception;
import org.springframework.security.saml2.core.Saml2X509Credential;
import org.springframework.stereotype.Component;
import de.ozgcloud.common.errorhandling.TechnicalException;
import de.ozgcloud.token.saml.ConfigurationEntity;
import de.ozgcloud.token.saml.SamlConfiguration;
import de.ozgcloud.token.saml.SamlConfigurationRegistry;
import de.ozgcloud.token.saml.SamlTokenUtils;
import lombok.RequiredArgsConstructor;
import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
import net.shibboleth.utilities.java.support.xml.ParserPool;
import net.shibboleth.utilities.java.support.xml.XMLParserException;
@Component
@Configuration
@RequiredArgsConstructor
public class TokenCheckConfiguration {
private XMLObjectProviderRegistry registry;
private final TokenCheckProperties tokenCheckerProperties;
private final ParserPool parserPool;
private final SamlConfigurationRegistry samlConfigurationRegistry;
private static final EncryptedKeyResolver encryptedKeyResolver = new ChainingEncryptedKeyResolver(
Arrays.asList(new InlineEncryptedKeyResolver(), new EncryptedElementTypeEncryptedKeyResolver(),
new SimpleRetrievalMethodEncryptedKeyResolver()));
@PostConstruct
public void initOpenSAML() {
try {
registry = new XMLObjectProviderRegistry();
ConfigurationService.register(XMLObjectProviderRegistry.class, registry);
registry.setParserPool(parserPool);
InitializationService.initialize();
tokenCheckerProperties.getEntities().forEach(this::initSamlConfiguration);
} catch (Exception e) {
throw new TechnicalException("Initialization failed", e);
}
}
private void initSamlConfiguration(final ConfigurationEntity entity) {
var trustEngine = initTrustEngine(entity.getMetadata(), entity.getIdpEntityId());
var verificationCriteria = getVerificationCriteria(entity.getIdpEntityId());
var decrypter = initDecrypter(entity);
var samlConfiguration = new SamlConfiguration(trustEngine, verificationCriteria, decrypter, entity.getMappings(),
entity.getUseIdAsPostkorbHandle());
samlConfigurationRegistry.addConfiguration(entity.getIdpEntityId(), samlConfiguration);
}
private SignatureTrustEngine initTrustEngine(Resource metadata, String idpEntityId) {
Set<Credential> credentials = new HashSet<>();
Collection<Saml2X509Credential> keys = getCertificatesFromMetadata(metadata);
for (Saml2X509Credential key : keys) {
var cred = new BasicX509Credential(key.getCertificate());
cred.setUsageType(UsageType.SIGNING);
cred.setEntityId(idpEntityId);
credentials.add(cred);
}
CredentialResolver credentialsResolver = new CollectionCredentialResolver(credentials);
return new ExplicitKeySignatureTrustEngine(credentialsResolver,
DefaultSecurityConfigurationBootstrap.buildBasicInlineKeyInfoCredentialResolver());
}
private CriteriaSet getVerificationCriteria(String idpEntityId) {
var criteria = new CriteriaSet();
criteria.add(new EvaluableEntityIDCredentialCriterion(new EntityIdCriterion(idpEntityId)));
criteria.add(new EvaluableProtocolRoleDescriptorCriterion(new ProtocolCriterion("urn:oasis:names:tc:SAML:2.0:protocol")));
criteria.add(new EvaluableUsageCredentialCriterion(new UsageCriterion(UsageType.SIGNING)));
return criteria;
}
private List<Saml2X509Credential> getCertificatesFromMetadata(Resource metadataResource) {
try (var metadata = metadataResource.getInputStream()) {
var xmlObject = xmlObject(metadata);
var descriptorOptional = findEntityDescriptor(xmlObject);
if (descriptorOptional.isPresent()) {
return SamlTokenUtils.getVerificationCertificates(descriptorOptional.get());
}
} catch (IOException e) {
throw new Saml2Exception("Error reading idp metadata.", e);
} catch (XMLParserException e) {
throw new Saml2Exception("Error initializing parser pool.", e);
}
throw new Saml2Exception("No IDPSSO Descriptors found");
}
XMLObject xmlObject(InputStream inputStream) throws XMLParserException {
var document = parserPool.parse(inputStream);
var element = document.getDocumentElement();
var unmarshaller = registry.getUnmarshallerFactory().getUnmarshaller(element);
if (unmarshaller == null) {
throw new Saml2Exception("Unsupported element of type " + element.getTagName());
}
try {
return unmarshaller.unmarshall(element);
} catch (Exception ex) {
throw new Saml2Exception(ex);
}
}
private Decrypter initDecrypter(ConfigurationEntity entity) {
var credentials = new ArrayList<Credential>();
var decryptionX509Credential = SamlTokenUtils.getDecryptionCredential(entity.getKey(), entity.getCertificate());
var cred = CredentialSupport.getSimpleCredential(decryptionX509Credential.getCertificate(), decryptionX509Credential.getPrivateKey());
credentials.add(cred);
var resolver = new CollectionKeyInfoCredentialResolver(credentials);
var setupDecrypter = new Decrypter(null, resolver, encryptedKeyResolver);
setupDecrypter.setRootInNewDocument(true);
return setupDecrypter;
}
}
/*
* Copyright (c) 2024.
* Lizenziert unter der EUPL, Version 1.2 oder - sobald
* diese von der Europäischen Kommission genehmigt wurden -
* Folgeversionen der EUPL ("Lizenz");
* Sie dürfen dieses Werk ausschließlich gemäß
* dieser Lizenz nutzen.
* Eine Kopie der Lizenz finden Sie hier:
*
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
*
* Sofern nicht durch anwendbare Rechtsvorschriften
* gefordert oder in schriftlicher Form vereinbart, wird
* die unter der Lizenz verbreitete Software "so wie sie
* ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
* ausdrücklich oder stillschweigend - verbreitet.
* Die sprachspezifischen Genehmigungen und Beschränkungen
* unter der Lizenz sind dem Lizenztext zu entnehmen.
*/
package de.ozgcloud.token;
import de.ozgcloud.token.saml.SamlTokenService;
import io.grpc.stub.StreamObserver;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import net.devh.boot.grpc.server.service.GrpcService;
@Log4j2
@GrpcService
@RequiredArgsConstructor
public class TokenCheckGrpcService extends TokenCheckServiceGrpc.TokenCheckServiceImplBase {
private final SamlTokenService samlTokenService;
private final TokenValidationResultMapper tokenCheckMapper;
@Override
public void checkToken(GrpcCheckTokenRequest request, StreamObserver<GrpcCheckTokenResponse> responseStreamObserver) {
var result = samlTokenService.validate(request.getToken());
responseStreamObserver.onNext(buildCheckResponse(result));
responseStreamObserver.onCompleted();
}
GrpcCheckTokenResponse buildCheckResponse(TokenValidationResult result) {
return result.isValid() ? buildValidCheckTokenResponse(result) : buildInvalidCheckTokenResponse(result);
}
GrpcCheckTokenResponse buildValidCheckTokenResponse(TokenValidationResult result) {
return GrpcCheckTokenResponse.newBuilder()
.setTokenValid(true)
.setTokenAttributes(tokenCheckMapper.toTokenAttributes(result)).build();
}
GrpcCheckTokenResponse buildInvalidCheckTokenResponse(TokenValidationResult result) {
return GrpcCheckTokenResponse.newBuilder()
.setTokenValid(false)
.setCheckErrors(tokenCheckMapper.toCheckErrors(result))
.build();
}
}
/*
* Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
* Ministerpräsidenten des Landes Schleswig-Holstein
* Staatskanzlei
* Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
*
* Lizenziert unter der EUPL, Version 1.2 oder - sobald
* diese von der Europäischen Kommission genehmigt wurden -
* Folgeversionen der EUPL ("Lizenz");
* Sie dürfen dieses Werk ausschließlich gemäß
* dieser Lizenz nutzen.
* Eine Kopie der Lizenz finden Sie hier:
*
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
*
* Sofern nicht durch anwendbare Rechtsvorschriften
* gefordert oder in schriftlicher Form vereinbart, wird
* die unter der Lizenz verbreitete Software "so wie sie
* ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
* ausdrücklich oder stillschweigend - verbreitet.
* Die sprachspezifischen Genehmigungen und Beschränkungen
* unter der Lizenz sind dem Lizenztext zu entnehmen.
*/
package de.ozgcloud.token;
import java.util.List;
import org.opensaml.saml.saml2.core.Response;
import org.springframework.stereotype.Service;
import de.ozgcloud.token.saml.Saml2DecryptionService;
import de.ozgcloud.token.saml.Saml2ParseService;
import de.ozgcloud.token.saml.Saml2VerificationService;
import de.ozgcloud.token.saml.SamlConfiguration;
import de.ozgcloud.token.saml.SamlConfigurationRegistry;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor
public class TokenCheckService {
public static final String POSTKORB_HANDLE_KEY = "postkorbHandle";
public static final String TRUST_LEVEL_KEY = "trustLevel";
private final SamlConfigurationRegistry samlConfigurationRegistry;
private final Saml2DecryptionService decryptionService;
private final Saml2ParseService parseService;
private final Saml2VerificationService verificationService;
public TokenCheckResult checkToken(final String token) {
var errors = verificationService.verify(token);
if (errors.isEmpty()) {
return getTokenCheckResult(token);
}
throw new TokenVerificationException("Errors occurred while checking token", errors);
}
TokenCheckResult getTokenCheckResult(final String token) {
var response = parseService.parse(token);
var samlConfiguration = samlConfigurationRegistry.getConfiguration(response.getIssuer().getValue());
var decryptedAttributes = decryptionService.decryptAttributes(response, samlConfiguration);
String postkorbHandle = getPostkorbHandle(samlConfiguration, response, decryptedAttributes);
return TokenCheckResult.builder().attributes(decryptedAttributes).postkorbHandle(postkorbHandle)
.trustLevel(findAttributeByKey(TRUST_LEVEL_KEY, decryptedAttributes, samlConfiguration)).build();
}
private String getPostkorbHandle(final SamlConfiguration samlConfiguration, final Response response,
final List<TokenAttribute> decryptedAttributes) {
String postkorbHandle;
if (samlConfiguration.idIsPostKorbMapping()) {
postkorbHandle = response.getID();
} else {
postkorbHandle = findAttributeByKey(POSTKORB_HANDLE_KEY, decryptedAttributes, samlConfiguration);
}
return postkorbHandle;
}
private String findAttributeByKey(String key, List<TokenAttribute> attributes, SamlConfiguration samlConfiguration) {
var name = samlConfiguration.mappings().get(key);
return attributes.stream().filter(attribute -> attribute.name().equals(name))
.findFirst().map(TokenAttribute::value).orElse("");
}
}
/* /*
* Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den * Copyright (c) 2024.
* Ministerpräsidenten des Landes Schleswig-Holstein
* Staatskanzlei
* Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
*
* Lizenziert unter der EUPL, Version 1.2 oder - sobald * Lizenziert unter der EUPL, Version 1.2 oder - sobald
* diese von der Europäischen Kommission genehmigt wurden - * diese von der Europäischen Kommission genehmigt wurden -
* Folgeversionen der EUPL ("Lizenz"); * Folgeversionen der EUPL ("Lizenz");
...@@ -21,38 +17,56 @@ ...@@ -21,38 +17,56 @@
* Die sprachspezifischen Genehmigungen und Beschränkungen * Die sprachspezifischen Genehmigungen und Beschränkungen
* unter der Lizenz sind dem Lizenztext zu entnehmen. * unter der Lizenz sind dem Lizenztext zu entnehmen.
*/ */
package de.ozgcloud.token; package de.ozgcloud.token;
import java.util.HashMap; import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan; import org.opensaml.core.config.ConfigurationService;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.opensaml.core.config.InitializationException;
import org.opensaml.core.config.InitializationService;
import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import de.ozgcloud.token.saml.SamlConfigurationRegistry;
import de.ozgcloud.token.saml.SamlTokenUtils;
import net.shibboleth.utilities.java.support.component.ComponentInitializationException; import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
import net.shibboleth.utilities.java.support.xml.BasicParserPool; import net.shibboleth.utilities.java.support.xml.BasicParserPool;
import net.shibboleth.utilities.java.support.xml.ParserPool; import net.shibboleth.utilities.java.support.xml.ParserPool;
@ConfigurationPropertiesScan("de.ozgcloud.token") @Configuration
@EnableConfigurationProperties(TokenCheckProperties.class) public class TokenCheckerConfiguration {
public class TokenCheckTestConfiguration {
public static final String FEATURES_EXTERNAL_GENERAL_ENTITIES = "http://xml.org/sax/features/external-general-entities";
public static final String FEATURES_EXTERNAL_PARAMETER_ENTITIES = "http://xml.org/sax/features/external-parameter-entities";
public static final String FEATURES_DISALLOW_DOCTYPE_DECL = "http://apache.org/xml/features/disallow-doctype-decl";
public static final String VALIDATION_SCHEMA_NORMALIZED_VALUE = "http://apache.org/xml/features/validation/schema/normalized-value";
public static final String FEATURE_SECURE_PROCESSING = "http://javax.xml.XMLConstants/feature/secure-processing";
@Bean @Bean
public ParserPool parserPool() throws ComponentInitializationException { ParserPool parserPool() throws ComponentInitializationException {
var localParserPool = new BasicParserPool(); var localParserPool = new BasicParserPool();
localParserPool.setBuilderFeatures(createFeatureMap());
final var features = SamlTokenUtils.createFeatureMap();
localParserPool.setBuilderFeatures(features);
localParserPool.setBuilderAttributes(new HashMap<>());
localParserPool.initialize(); localParserPool.initialize();
return localParserPool; return localParserPool;
} }
Map<String, Boolean> createFeatureMap() {
return Map.of(
FEATURES_EXTERNAL_GENERAL_ENTITIES, Boolean.FALSE,
FEATURES_EXTERNAL_PARAMETER_ENTITIES, Boolean.FALSE,
FEATURES_DISALLOW_DOCTYPE_DECL, Boolean.TRUE,
VALIDATION_SCHEMA_NORMALIZED_VALUE, Boolean.FALSE,
FEATURE_SECURE_PROCESSING, Boolean.TRUE);
}
@Bean @Bean
public SamlConfigurationRegistry samlConfigurationRegistry() { XMLObjectProviderRegistry xmlObjectProviderRegistry(ParserPool parserPool) throws InitializationException {
return new SamlConfigurationRegistry(); var registry = new XMLObjectProviderRegistry();
registry.setParserPool(parserPool);
ConfigurationService.register(XMLObjectProviderRegistry.class, registry);
InitializationService.initialize();
return registry;
} }
} }
/* /*
* Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den * Copyright (c) 2024.
* Ministerpräsidenten des Landes Schleswig-Holstein
* Staatskanzlei
* Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
*
* Lizenziert unter der EUPL, Version 1.2 oder - sobald * Lizenziert unter der EUPL, Version 1.2 oder - sobald
* diese von der Europäischen Kommission genehmigt wurden - * diese von der Europäischen Kommission genehmigt wurden -
* Folgeversionen der EUPL ("Lizenz"); * Folgeversionen der EUPL ("Lizenz");
...@@ -21,26 +17,76 @@ ...@@ -21,26 +17,76 @@
* Die sprachspezifischen Genehmigungen und Beschränkungen * Die sprachspezifischen Genehmigungen und Beschränkungen
* unter der Lizenz sind dem Lizenztext zu entnehmen. * unter der Lizenz sind dem Lizenztext zu entnehmen.
*/ */
package de.ozgcloud.token; package de.ozgcloud.token;
import java.util.List; import java.util.List;
import java.util.Map;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import de.ozgcloud.token.saml.ConfigurationEntity;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.ToString;
@Setter @Setter
@Getter @Getter
@ConfigurationProperties(prefix = TokenCheckProperties.PREFIX) @Configuration
public class TokenCheckProperties { @ConfigurationProperties(prefix = TokenValidationProperties.PREFIX)
public class TokenValidationProperties {
static final String PREFIX = "ozgcloud.token.check"; static final String PREFIX = "ozgcloud.token.check";
/** /**
* List of entities. A ConfigurationEntity contains the necessary information for verifying and decrypting saml tokens. * List of entities. A ConfigurationEntity contains the necessary information for verifying and decrypting saml tokens.
*/ */
@NotEmpty @NotEmpty
private List<ConfigurationEntity> entities; @Valid
private List<TokenValidationProperty> entities;
@Getter
@Setter
@ToString
public static class TokenValidationProperty {
/**
* The id of the Identity Provider, this is also the issuer value.
*/
@NotEmpty
private String idpEntityId;
/**
* The encryption key
*/
@NotEmpty
private Resource key;
/**
* The encryption certificate
*/
@NotEmpty
private Resource certificate;
/**
* The url or the actual SAML Metadata file received from the idp
*/
@NotEmpty
private Resource metadata;
/**
* Use the user id as Postkorbhandle. For Muk
*/
private boolean useIdAsPostfachId = false;
/**
* The mappings define the names of the attributes in the SamlResponse that correspond to these keys
*/
@NotNull
private Map<String, String> mappings;
}
} }
...@@ -25,14 +25,18 @@ package de.ozgcloud.token; ...@@ -25,14 +25,18 @@ package de.ozgcloud.token;
import java.util.List; import java.util.List;
import org.mapstruct.CollectionMappingStrategy; import de.ozgcloud.token.common.errorhandling.ValidationError;
import org.mapstruct.Mapper; import lombok.Builder;
import org.mapstruct.Mapping; import lombok.Getter;
import lombok.Singular;
@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED) @Builder
interface TokenCheckMapper { @Getter
@Mapping(source = "attributes", target = "otherFieldsList") public class TokenValidationResult {
GrpcTokenCheckResponse toGrpcTokenResponse(TokenCheckResult result);
private final boolean valid;
private final TokenAttributes attributes;
@Singular
private final List<ValidationError> validationErrors;
List<GrpcTokenAttribute> toGrpcTokenAttributeList(List<TokenAttribute> attributes);
} }
/*
* Copyright (c) 2024.
* Lizenziert unter der EUPL, Version 1.2 oder - sobald
* diese von der Europäischen Kommission genehmigt wurden -
* Folgeversionen der EUPL ("Lizenz");
* Sie dürfen dieses Werk ausschließlich gemäß
* dieser Lizenz nutzen.
* Eine Kopie der Lizenz finden Sie hier:
*
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
*
* Sofern nicht durch anwendbare Rechtsvorschriften
* gefordert oder in schriftlicher Form vereinbart, wird
* die unter der Lizenz verbreitete Software "so wie sie
* ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
* ausdrücklich oder stillschweigend - verbreitet.
* Die sprachspezifischen Genehmigungen und Beschränkungen
* unter der Lizenz sind dem Lizenztext zu entnehmen.
*/
package de.ozgcloud.token;
import org.mapstruct.CollectionMappingStrategy;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.NullValueCheckStrategy;
import org.mapstruct.NullValueMappingStrategy;
import org.mapstruct.NullValuePropertyMappingStrategy;
import org.mapstruct.ReportingPolicy;
import de.ozgcloud.token.common.errorhandling.ValidationError;
@Mapper(unmappedTargetPolicy = ReportingPolicy.WARN, collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED, //
nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS, nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE,
nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT)
interface TokenValidationResultMapper {
@Mapping(target = "unknownFields", ignore = true)
@Mapping(target = "trustLevelBytes", ignore = true)
@Mapping(target = "removeOtherFields", ignore = true)
@Mapping(target = "postfachIdBytes", ignore = true)
@Mapping(target = "otherFieldsOrBuilderList", ignore = true)
@Mapping(target = "otherFieldsBuilderList", ignore = true)
@Mapping(target = "mergeUnknownFields", ignore = true)
@Mapping(target = "mergeFrom", ignore = true)
@Mapping(target = "defaultInstanceForType", ignore = true)
@Mapping(target = "clearOneof", ignore = true)
@Mapping(target = "clearField", ignore = true)
@Mapping(target = "allFields", ignore = true)
@Mapping(target = "postfachId", source = "attributes.postfachId")
@Mapping(target = "trustLevel", source = "attributes.trustLevel")
@Mapping(target = "otherFieldsList", source = "attributes.otherAttributes")
GrpcTokenAttributes toTokenAttributes(TokenValidationResult validationResult);
@Mapping(target = "unknownFields", ignore = true)
@Mapping(target = "removeCheckError", ignore = true)
@Mapping(target = "mergeUnknownFields", ignore = true)
@Mapping(target = "mergeFrom", ignore = true)
@Mapping(target = "defaultInstanceForType", ignore = true)
@Mapping(target = "clearOneof", ignore = true)
@Mapping(target = "clearField", ignore = true)
@Mapping(target = "checkErrorOrBuilderList", ignore = true)
@Mapping(target = "checkErrorBuilderList", ignore = true)
@Mapping(target = "allFields", ignore = true)
@Mapping(target = "checkErrorList", source = "validationErrors")
GrpcCheckErrors toCheckErrors(TokenValidationResult validationResult);
@Mapping(target = "unknownFields", ignore = true)
@Mapping(target = "messageBytes", ignore = true)
@Mapping(target = "mergeUnknownFields", ignore = true)
@Mapping(target = "mergeFrom", ignore = true)
@Mapping(target = "defaultInstanceForType", ignore = true)
@Mapping(target = "clearOneof", ignore = true)
@Mapping(target = "clearField", ignore = true)
@Mapping(target = "allFields", ignore = true)
GrpcCheckError toCheckError(ValidationError validationError);
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment