From b670c3ed558d3e2b25199e1ab2a4713f7c70cf9d Mon Sep 17 00:00:00 2001 From: Michael Simon <simon@kit.edu> Date: Mon, 23 Sep 2024 12:42:21 +0200 Subject: [PATCH] ISSUE-196 make account resolver scriptable for resolving an account for a nameid. --- .../saml/SamlAttributeQueryService.java | 66 +-------------- .../saml/SamlAttributeQueryServiceImpl.java | 75 +++++++++++++++++ .../saml/idp/AttributeAuthorityService.java | 81 ++++++++++++++----- .../sp/as/AttributeQueryAttributeSource.java | 2 - .../service/saml/AttributeQueryHelper.java | 1 - .../service/saml/Saml2AssertionService.java | 6 +- .../saml/Saml2ResponseValidationService.java | 5 +- 7 files changed, 144 insertions(+), 92 deletions(-) create mode 100644 bwreg-service/src/main/java/edu/kit/scc/webreg/service/saml/SamlAttributeQueryServiceImpl.java diff --git a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/saml/SamlAttributeQueryService.java b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/saml/SamlAttributeQueryService.java index 12fb79aa0..01bf78d90 100644 --- a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/saml/SamlAttributeQueryService.java +++ b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/saml/SamlAttributeQueryService.java @@ -3,73 +3,13 @@ package edu.kit.scc.webreg.service.saml; import java.io.IOException; import java.io.Serializable; -import org.opensaml.messaging.decoder.MessageDecodingException; -import org.opensaml.saml.saml2.core.AttributeQuery; -import org.opensaml.saml.saml2.core.StatusCode; -import org.opensaml.soap.soap11.Envelope; -import org.slf4j.Logger; - -import edu.kit.scc.webreg.annotations.RetryTransaction; import edu.kit.scc.webreg.entity.SamlAAConfigurationEntity; -import edu.kit.scc.webreg.saml.idp.AttributeAuthorityService; -import edu.kit.scc.webreg.service.saml.exc.SamlAuthenticationException; -import jakarta.ejb.Stateless; -import jakarta.ejb.TransactionManagement; -import jakarta.ejb.TransactionManagementType; -import jakarta.inject.Inject; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import net.shibboleth.shared.component.ComponentInitializationException; - -@Stateless -@TransactionManagement(TransactionManagementType.BEAN) -public class SamlAttributeQueryService implements Serializable { - - private static final long serialVersionUID = 1L; - - @Inject - private Logger logger; - - @Inject - private AttributeAuthorityService aaService; - - @Inject - private Saml2DecoderService saml2DecoderService; - - @Inject - private SamlHelper samlHelper; - - @RetryTransaction - public void consumeAttributeQuery(HttpServletRequest request, HttpServletResponse response, - SamlAAConfigurationEntity aaConfig) throws IOException { - logger.debug("Consuming SAML AttributeQuery"); - - try { - AttributeQuery query = saml2DecoderService.decodeAttributeQuery(request); - logger.debug("SAML AttributeQuery decoded"); - Envelope envelope = aaService.processAttributeQuery(aaConfig, query); - response.getWriter().print(samlHelper.marshal(envelope)); +public interface SamlAttributeQueryService extends Serializable { - } catch (MessageDecodingException e) { - logger.info("Could not execute AttributeQuery: {}", e.getMessage()); - sendErrorResponse(response, StatusCode.REQUEST_DENIED, e.getMessage()); - } catch (SecurityException e) { - logger.info("Could not execute AttributeQuery: {}", e.getMessage()); - sendErrorResponse(response, StatusCode.REQUEST_DENIED, e.getMessage()); - } catch (SamlAuthenticationException e) { - logger.info("Could not execute AttributeQuery: {}", e.getMessage()); - sendErrorResponse(response, StatusCode.REQUEST_DENIED, e.getMessage()); - } catch (ComponentInitializationException e) { - logger.info("Could not execute AttributeQuery: {}", e.getMessage()); - sendErrorResponse(response, StatusCode.REQUEST_DENIED, e.getMessage()); - } - } - - private void sendErrorResponse(HttpServletResponse response, String statusCodeString, String messageString) - throws IOException { - Envelope envelope = aaService.buildErrorResponse(statusCodeString, messageString); - response.getWriter().print(samlHelper.marshal(envelope)); - } + void consumeAttributeQuery(HttpServletRequest request, HttpServletResponse response, + SamlAAConfigurationEntity aaConfig) throws IOException; } diff --git a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/saml/SamlAttributeQueryServiceImpl.java b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/saml/SamlAttributeQueryServiceImpl.java new file mode 100644 index 000000000..5786167b1 --- /dev/null +++ b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/saml/SamlAttributeQueryServiceImpl.java @@ -0,0 +1,75 @@ +package edu.kit.scc.webreg.service.saml; + +import java.io.IOException; + +import org.opensaml.messaging.decoder.MessageDecodingException; +import org.opensaml.saml.saml2.core.AttributeQuery; +import org.opensaml.saml.saml2.core.StatusCode; +import org.opensaml.soap.soap11.Envelope; +import org.slf4j.Logger; + +import edu.kit.scc.webreg.annotations.RetryTransaction; +import edu.kit.scc.webreg.entity.SamlAAConfigurationEntity; +import edu.kit.scc.webreg.saml.idp.AttributeAuthorityService; +import edu.kit.scc.webreg.service.saml.exc.SamlAuthenticationException; +import jakarta.ejb.Stateless; +import jakarta.ejb.TransactionManagement; +import jakarta.ejb.TransactionManagementType; +import jakarta.inject.Inject; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import net.shibboleth.shared.component.ComponentInitializationException; + +@Stateless +@TransactionManagement(TransactionManagementType.BEAN) +public class SamlAttributeQueryServiceImpl implements SamlAttributeQueryService { + + private static final long serialVersionUID = 1L; + + @Inject + private Logger logger; + + @Inject + private AttributeAuthorityService aaService; + + @Inject + private Saml2DecoderService saml2DecoderService; + + @Inject + private SamlHelper samlHelper; + + @Override + @RetryTransaction + public void consumeAttributeQuery(HttpServletRequest request, HttpServletResponse response, + SamlAAConfigurationEntity aaConfig) throws IOException { + logger.debug("Consuming SAML AttributeQuery"); + + try { + AttributeQuery query = saml2DecoderService.decodeAttributeQuery(request); + logger.debug("SAML AttributeQuery decoded"); + + Envelope envelope = aaService.processAttributeQuery(aaConfig, query); + + response.getWriter().print(samlHelper.marshal(envelope)); + + } catch (MessageDecodingException e) { + logger.info("Could not execute AttributeQuery: {}", e.getMessage()); + sendErrorResponse(response, StatusCode.REQUEST_DENIED, e.getMessage()); + } catch (SecurityException e) { + logger.info("Could not execute AttributeQuery: {}", e.getMessage()); + sendErrorResponse(response, StatusCode.REQUEST_DENIED, e.getMessage()); + } catch (SamlAuthenticationException e) { + logger.info("Could not execute AttributeQuery: {}", e.getMessage()); + sendErrorResponse(response, StatusCode.REQUEST_DENIED, e.getMessage()); + } catch (ComponentInitializationException e) { + logger.info("Could not execute AttributeQuery: {}", e.getMessage()); + sendErrorResponse(response, StatusCode.REQUEST_DENIED, e.getMessage()); + } + } + + private void sendErrorResponse(HttpServletResponse response, String statusCodeString, String messageString) + throws IOException { + Envelope envelope = aaService.buildErrorResponse(statusCodeString, messageString); + response.getWriter().print(samlHelper.marshal(envelope)); + } +} diff --git a/regapp-saml-idp/src/main/java/edu/kit/scc/webreg/saml/idp/AttributeAuthorityService.java b/regapp-saml-idp/src/main/java/edu/kit/scc/webreg/saml/idp/AttributeAuthorityService.java index ce8b88ac5..448fb979f 100644 --- a/regapp-saml-idp/src/main/java/edu/kit/scc/webreg/saml/idp/AttributeAuthorityService.java +++ b/regapp-saml-idp/src/main/java/edu/kit/scc/webreg/saml/idp/AttributeAuthorityService.java @@ -2,6 +2,11 @@ package edu.kit.scc.webreg.saml.idp; import java.time.Instant; +import javax.script.Invocable; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; + import org.opensaml.core.xml.XMLObject; import org.opensaml.core.xml.XMLObjectBuilderFactory; import org.opensaml.core.xml.schema.XSString; @@ -22,10 +27,12 @@ import org.opensaml.soap.soap11.Envelope; import org.slf4j.Logger; import edu.kit.scc.webreg.dao.SamlSpMetadataDao; -import edu.kit.scc.webreg.dao.UserDao; +import edu.kit.scc.webreg.dao.ScriptDao; import edu.kit.scc.webreg.entity.SamlAAConfigurationEntity; import edu.kit.scc.webreg.entity.SamlSpMetadataEntity; +import edu.kit.scc.webreg.entity.ScriptEntity; import edu.kit.scc.webreg.entity.UserEntity; +import edu.kit.scc.webreg.script.ScriptingEnv; import edu.kit.scc.webreg.service.saml.Saml2ResponseValidationService; import edu.kit.scc.webreg.service.saml.SamlHelper; import edu.kit.scc.webreg.service.saml.SsoHelper; @@ -46,18 +53,22 @@ public class AttributeAuthorityService { private SamlSpMetadataDao spMetadataDao; @Inject - private UserDao userService; - + private ScriptDao scriptDao; + + @Inject + private ScriptingEnv scriptingEnv; + @Inject private SamlHelper samlHelper; @Inject private SsoHelper ssoHelper; - public Envelope processAttributeQuery(SamlAAConfigurationEntity aaConfig, AttributeQuery query) throws SamlAuthenticationException { - + public Envelope processAttributeQuery(SamlAAConfigurationEntity aaConfig, AttributeQuery query) + throws SamlAuthenticationException { + logger.debug("Processing AttributeQuery"); - + Issuer issuer = query.getIssuer(); if (issuer == null || issuer.getValue() == null) { throw new SamlAuthenticationException("Issuer not set"); @@ -68,6 +79,12 @@ public class AttributeAuthorityService { if (spEntity == null) throw new SamlAuthenticationException("Issuer metadata not in database"); + if (!spEntity.getGenericStore().containsKey("aq_allowed")) + throw new SamlAuthenticationException("Issuer not allowed for attribute query"); + else if (!spEntity.getGenericStore().get("aq_allowed").equalsIgnoreCase("true")) { + throw new SamlAuthenticationException("Issuer not allowed for attribute query"); + } + EntityDescriptor spEntityDescriptor = samlHelper.unmarshal(spEntity.getEntityDescriptor(), EntityDescriptor.class); @@ -79,29 +96,53 @@ public class AttributeAuthorityService { samlResponse.setIssueInstant(Instant.now()); if (query.getSubject() != null && query.getSubject().getNameID() != null) { - String nameIdValue = query.getSubject().getNameID().getValue(); - String nameIdFormat = query.getSubject().getNameID().getFormat(); - - UserEntity user = userService.fetch(Long.parseLong(nameIdValue)); - if (user != null) { - Assertion assertion = samlHelper.create(Assertion.class, Assertion.DEFAULT_ELEMENT_NAME); - assertion.setIssueInstant(Instant.now()); - assertion.setIssuer(ssoHelper.buildIssuser(aaConfig.getEntityId())); - assertion.setSubject(ssoHelper.buildAQSubject(aaConfig, spEntity, nameIdValue, NameID.UNSPECIFIED, - query.getID())); - assertion.getAttributeStatements().add(buildAttributeStatement(user)); - samlResponse.getAssertions().add(assertion); + + if (!spEntity.getGenericStore().containsKey("aq_resolve_user_scipt")) + throw new SamlAuthenticationException( + "NameId Resolver not configured, cannot resolve account. This is a server side error, contact the server administrator"); + String resolveUserScript = spEntity.getGenericStore().get("aq_resolve_user_scipt"); + ScriptEntity script = scriptDao.findByName(resolveUserScript); + if (script == null) + throw new SamlAuthenticationException("NameId Resolver not configured correctly. Script not found: " + resolveUserScript); + + try { + ScriptEngine engine = (new ScriptEngineManager()).getEngineByName(script.getScriptEngine()); + engine.eval(script.getScript()); + Invocable invocable = (Invocable) engine; + + String nameIdValue = query.getSubject().getNameID().getValue(); + String nameIdFormat = query.getSubject().getNameID().getFormat(); + + UserEntity user = (UserEntity) invocable.invokeFunction("resolveUser", scriptingEnv, nameIdFormat, nameIdValue, logger, + spEntity, aaConfig); + if (user != null) { + Assertion assertion = samlHelper.create(Assertion.class, Assertion.DEFAULT_ELEMENT_NAME); + assertion.setIssueInstant(Instant.now()); + assertion.setIssuer(ssoHelper.buildIssuser(aaConfig.getEntityId())); + assertion.setSubject( + ssoHelper.buildAQSubject(aaConfig, spEntity, nameIdValue, NameID.UNSPECIFIED, query.getID())); + assertion.getAttributeStatements().add(buildAttributeStatement(user)); + samlResponse.getAssertions().add(assertion); + } + } catch (NoSuchMethodException e) { + logger.warn("Method is missing in script: {}", e.getMessage()); + throw new SamlAuthenticationException("NameId Resolver not configured correctly. Method resolveUser not found in " + resolveUserScript); + } catch (ScriptException e) { + logger.warn("Script contains errors: {}", e.getMessage()); + throw new SamlAuthenticationException("NameId Resolver "+ resolveUserScript + " contains errors"); } + } else { + throw new SamlAuthenticationException("Subject or Subject Name ID is missing in request"); } return buildSoapEnvelope(samlResponse); } - + public Envelope buildErrorResponse(String statusCodeString, String messageString) { Response samlResponse = buildSamlRespone(statusCodeString, messageString); return buildSoapEnvelope(samlResponse); } - + private Response buildSamlRespone(String statusCodeString, String messageString) { Response samlResponse = samlHelper.create(Response.class, Response.DEFAULT_ELEMENT_NAME); samlResponse.setStatus(buildSamlStatus(statusCodeString, messageString)); diff --git a/regapp-saml-sp/src/main/java/edu/kit/scc/regapp/saml/sp/as/AttributeQueryAttributeSource.java b/regapp-saml-sp/src/main/java/edu/kit/scc/regapp/saml/sp/as/AttributeQueryAttributeSource.java index 70715ec60..b15789c80 100644 --- a/regapp-saml-sp/src/main/java/edu/kit/scc/regapp/saml/sp/as/AttributeQueryAttributeSource.java +++ b/regapp-saml-sp/src/main/java/edu/kit/scc/regapp/saml/sp/as/AttributeQueryAttributeSource.java @@ -20,8 +20,6 @@ import edu.kit.scc.webreg.dao.SamlIdpMetadataDao; import edu.kit.scc.webreg.dao.SamlSpConfigurationDao; import edu.kit.scc.webreg.dao.ScriptDao; import edu.kit.scc.webreg.dao.as.ASUserAttrValueDao; -import edu.kit.scc.webreg.entity.SamlAAMetadataEntity; -import edu.kit.scc.webreg.entity.SamlIdpMetadataEntity; import edu.kit.scc.webreg.entity.SamlMetadataEntity; import edu.kit.scc.webreg.entity.SamlSpConfigurationEntity; import edu.kit.scc.webreg.entity.ScriptEntity; diff --git a/regapp-saml/src/main/java/edu/kit/scc/webreg/service/saml/AttributeQueryHelper.java b/regapp-saml/src/main/java/edu/kit/scc/webreg/service/saml/AttributeQueryHelper.java index 3db6a5d33..26ca0ab16 100644 --- a/regapp-saml/src/main/java/edu/kit/scc/webreg/service/saml/AttributeQueryHelper.java +++ b/regapp-saml/src/main/java/edu/kit/scc/webreg/service/saml/AttributeQueryHelper.java @@ -26,7 +26,6 @@ import java.util.Set; import javax.net.ssl.SSLContext; -import org.apache.hc.client5.http.config.ConnectionConfig; import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.HttpClients; diff --git a/regapp-saml/src/main/java/edu/kit/scc/webreg/service/saml/Saml2AssertionService.java b/regapp-saml/src/main/java/edu/kit/scc/webreg/service/saml/Saml2AssertionService.java index dd5667f50..ee8979599 100644 --- a/regapp-saml/src/main/java/edu/kit/scc/webreg/service/saml/Saml2AssertionService.java +++ b/regapp-saml/src/main/java/edu/kit/scc/webreg/service/saml/Saml2AssertionService.java @@ -22,8 +22,6 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; import javax.script.Invocable; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; @@ -62,6 +60,8 @@ import edu.kit.scc.webreg.entity.SamlUserEntity_; import edu.kit.scc.webreg.entity.ScriptEntity; import edu.kit.scc.webreg.service.saml.exc.NoAssertionException; import edu.kit.scc.webreg.service.saml.exc.SamlAuthenticationException; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; @ApplicationScoped public class Saml2AssertionService { @@ -100,7 +100,7 @@ public class Saml2AssertionService { } saml2ValidationService.verifyStatus(samlResponse); - saml2ValidationService.verifyIssuer((SamlIdpMetadataEntity) idpEntity, samlResponse); + saml2ValidationService.verifyIssuer(idpEntity, samlResponse); saml2ValidationService.verifyExpiration(samlResponse, 1000L * 60L * 10L); Boolean responseSignatureValid = false; diff --git a/regapp-saml/src/main/java/edu/kit/scc/webreg/service/saml/Saml2ResponseValidationService.java b/regapp-saml/src/main/java/edu/kit/scc/webreg/service/saml/Saml2ResponseValidationService.java index 1e2a25a24..a9ed2f115 100644 --- a/regapp-saml/src/main/java/edu/kit/scc/webreg/service/saml/Saml2ResponseValidationService.java +++ b/regapp-saml/src/main/java/edu/kit/scc/webreg/service/saml/Saml2ResponseValidationService.java @@ -39,7 +39,6 @@ import org.opensaml.xmlsec.signature.support.SignatureException; import org.opensaml.xmlsec.signature.support.impl.ExplicitKeySignatureTrustEngine; import org.slf4j.Logger; -import edu.kit.scc.webreg.entity.SamlIdpMetadataEntity; import edu.kit.scc.webreg.entity.SamlMetadataEntity; import edu.kit.scc.webreg.entity.SamlSpMetadataEntity; import edu.kit.scc.webreg.service.saml.exc.SamlAuthenticationException; @@ -86,12 +85,12 @@ public class Saml2ResponseValidationService { } - public void verifyIssuer(SamlIdpMetadataEntity metadataEntity, + public void verifyIssuer(SamlMetadataEntity metadataEntity, Response samlResponse) throws SamlAuthenticationException { verifyIssuer(metadataEntity, samlResponse.getIssuer()); } - public void verifyIssuer(SamlIdpMetadataEntity metadataEntity, + public void verifyIssuer(SamlMetadataEntity metadataEntity, Issuer issuer) throws SamlAuthenticationException { if (issuer == null) -- GitLab