diff --git a/bwreg-entities/src/main/java/edu/kit/scc/webreg/entity/SamlAuthnRequestEntity.java b/bwreg-entities/src/main/java/edu/kit/scc/webreg/entity/SamlAuthnRequestEntity.java index 2a908659d801d84cc02a0c16cc6e3ab570807a95..43f2ac90957e7769d5b762f4e4aa0bcda506c645 100644 --- a/bwreg-entities/src/main/java/edu/kit/scc/webreg/entity/SamlAuthnRequestEntity.java +++ b/bwreg-entities/src/main/java/edu/kit/scc/webreg/entity/SamlAuthnRequestEntity.java @@ -5,11 +5,13 @@ import java.util.Date; import org.hibernate.annotations.JdbcTypeCode; +import edu.kit.scc.webreg.entity.attribute.AttributeReleaseEntity; import jakarta.persistence.Basic; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.Lob; +import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; @Entity(name = "SamlAuthnRequestEntity") @@ -24,6 +26,12 @@ public class SamlAuthnRequestEntity extends AbstractBaseEntity { @JdbcTypeCode(Types.LONGVARCHAR) private String authnrequestData; + @ManyToOne(targetEntity = AttributeReleaseEntity.class) + private AttributeReleaseEntity attributeRelease; + + @ManyToOne(targetEntity = SamlSpMetadataEntity.class) + private SamlSpMetadataEntity spMetadata; + @Column(name = "valid_until") private Date validUntil; @@ -43,4 +51,20 @@ public class SamlAuthnRequestEntity extends AbstractBaseEntity { this.authnrequestData = authnrequestData; } + public AttributeReleaseEntity getAttributeRelease() { + return attributeRelease; + } + + public void setAttributeRelease(AttributeReleaseEntity attributeRelease) { + this.attributeRelease = attributeRelease; + } + + public SamlSpMetadataEntity getSpMetadata() { + return spMetadata; + } + + public void setSpMetadata(SamlSpMetadataEntity spMetadata) { + this.spMetadata = spMetadata; + } + } diff --git a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/attributes/AttributeReleaseService.java b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/attributes/AttributeReleaseService.java index ecfe8261534c006bc44ac46ca2229103d5a263a5..96d2979224e1b0cac8fc343af17ba5ce268e46bb 100644 --- a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/attributes/AttributeReleaseService.java +++ b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/attributes/AttributeReleaseService.java @@ -35,14 +35,10 @@ public class AttributeReleaseService extends BaseServiceImpl<AttributeReleaseEnt } public AttributeReleaseEntity accept(AttributeReleaseEntity attributeRelease, OidcFlowStateEntity flowState, IdentityEntity identity) { - attributeRelease = dao.fetch(attributeRelease.getId()); - attributeRelease.setReleaseStatus(ReleaseStatusType.GOOD); - attributeRelease.setIssuedAt(new Date()); - flowState = flowStateDao.fetch(flowState.getId()); flowState.setValidUntil(new Date(System.currentTimeMillis() + (10L * 60L * 1000L))); - return attributeRelease; + return accept(attributeRelease); } public AttributeReleaseEntity accept(AttributeReleaseEntity attributeRelease) { @@ -59,15 +55,19 @@ public class AttributeReleaseService extends BaseServiceImpl<AttributeReleaseEnt return attributeRelease; } - public AttributeReleaseEntity reject(AttributeReleaseEntity attributeRelease, OidcFlowStateEntity flowState, IdentityEntity identity) { + public AttributeReleaseEntity reject(AttributeReleaseEntity attributeRelease) { attributeRelease = dao.fetch(attributeRelease.getId()); attributeRelease.setReleaseStatus(ReleaseStatusType.REJECTED); attributeRelease.setIssuedAt(new Date()); + return attributeRelease; + } + + public AttributeReleaseEntity reject(AttributeReleaseEntity attributeRelease, OidcFlowStateEntity flowState, IdentityEntity identity) { flowState = flowStateDao.fetch(flowState.getId()); flowState.setValidUntil(new Date(System.currentTimeMillis() + (10L * 60L * 1000L))); - return attributeRelease; + return reject(attributeRelease); } @Override diff --git a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/saml/SamlIdpServiceImpl.java b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/saml/SamlIdpServiceImpl.java index d14c0e9d37bda9bd36900854f2a25e1c66d92379..1a09087a83e0bb1e96a729efb0a721640c84725d 100644 --- a/bwreg-service/src/main/java/edu/kit/scc/webreg/service/saml/SamlIdpServiceImpl.java +++ b/bwreg-service/src/main/java/edu/kit/scc/webreg/service/saml/SamlIdpServiceImpl.java @@ -88,6 +88,7 @@ import edu.kit.scc.webreg.entity.ServiceEntity; import edu.kit.scc.webreg.entity.ServiceSamlSpEntity; import edu.kit.scc.webreg.entity.UserEntity; import edu.kit.scc.webreg.entity.attribute.AttributeReleaseEntity; +import edu.kit.scc.webreg.entity.attribute.ReleaseStatusType; import edu.kit.scc.webreg.entity.identity.IdentityEntity; import edu.kit.scc.webreg.saml.idp.SamlAttributeTranscoder; import edu.kit.scc.webreg.service.attribute.release.AttributeBuilder; @@ -152,7 +153,7 @@ public class SamlIdpServiceImpl implements SamlIdpService { @Inject private ApplicationConfig appConfig; - + @Inject private AttributeBuilder attributeBuilder; @@ -196,7 +197,8 @@ public class SamlIdpServiceImpl implements SamlIdpService { SamlSpMetadataEntity spMetadata = spDao.findByEntityId(authnRequest.getIssuer().getValue()); logger.debug("Corresponding SP found in Metadata: {}", spMetadata.getEntityId()); - + authnRequestEntity.setSpMetadata(spMetadata); + List<ServiceSamlSpEntity> serviceSamlSpEntityList = serviceSamlSpDao.findBySamlSp(spMetadata); if (serviceSamlSpEntityList.size() == 0) { @@ -282,10 +284,11 @@ public class SamlIdpServiceImpl implements SamlIdpService { * There is no service set for this sp idp connection */ filteredServiceSamlSpEntityList.add(serviceSamlSpEntity); - List<String> unauthorizedList = knowledgeSessionService.checkScriptAccess(serviceSamlSpEntity.getScript(), identity); + List<String> unauthorizedList = knowledgeSessionService + .checkScriptAccess(serviceSamlSpEntity.getScript(), identity); if (unauthorizedList.size() > 0) { return "/user/saml-access-denied.xhtml?soidc=" + serviceSamlSpEntity.getId(); - } + } } } else { logger.debug("serviceSamlSpEntity no match: {}", serviceSamlSpEntity.getId()); @@ -316,8 +319,10 @@ public class SamlIdpServiceImpl implements SamlIdpService { assertion.setIssuer(ssoHelper.buildIssuser(idpConfig.getEntityId())); assertion.setConditions(ssoHelper.buildConditions(spMetadata)); - buildAttributeStatement(idpConfig, spMetadata, authnRequest, assertion, user, filteredServiceSamlSpEntityList, - registry); + String returnUrl = buildAttributeStatement(idpConfig, spMetadata, authnRequest, assertion, user, + filteredServiceSamlSpEntityList, registry, authnRequestEntity); + if (returnUrl != null) + return returnUrl; long validity = 30L * 60L * 1000L; if (spMetadata.getGenericStore().containsKey("session_validity")) { @@ -563,19 +568,19 @@ public class SamlIdpServiceImpl implements SamlIdpService { return false; } - private void buildAttributeStatement(final SamlIdpConfigurationEntity idpConfig, final SamlSpMetadataEntity spMetadata, - final AuthnRequest authnRequest, final Assertion assertion, final UserEntity user, - final List<ServiceSamlSpEntity> serviceSamlSpEntityList, final RegistryEntity registry) - throws SamlAuthenticationException { + private String buildAttributeStatement(final SamlIdpConfigurationEntity idpConfig, + final SamlSpMetadataEntity spMetadata, final AuthnRequest authnRequest, final Assertion assertion, + final UserEntity user, final List<ServiceSamlSpEntity> serviceSamlSpEntityList, + final RegistryEntity registry, final SamlAuthnRequestEntity authnRequestEntity) throws SamlAuthenticationException { List<Attribute> attributeList = new ArrayList<>(); Boolean subjectOverride = false; final IdentityEntity identity = user.getIdentity(); - final AttributeReleaseEntity attributeRelease = attributeBuilder.requestAttributeRelease(spMetadata, - identity); + final AttributeReleaseEntity attributeRelease = attributeBuilder.requestAttributeRelease(spMetadata, identity); + authnRequestEntity.setAttributeRelease(attributeRelease); attributeRelease.setValuesToDelete(new HashSet<>(attributeRelease.getValues())); - + for (ServiceSamlSpEntity serviceSamlSp : serviceSamlSpEntityList) { ScriptEntity scriptEntity = serviceSamlSp.getScript(); if (scriptEntity.getScriptType().equalsIgnoreCase("javascript")) { @@ -625,7 +630,8 @@ public class SamlIdpServiceImpl implements SamlIdpService { } attributeRelease.getValuesToDelete().stream().forEach(v -> attributeBuilder.deleteValue(v)); - final AttributeStatement attributeStatement = attributeTranscoder.convertAttributeStatement(attributeRelease, idpConfig, spMetadata); + final AttributeStatement attributeStatement = attributeTranscoder.convertAttributeStatement(attributeRelease, + idpConfig, spMetadata); for (Attribute attribute : attributeList) { attributeStatement.getAttributes().add(attribute); @@ -636,7 +642,19 @@ public class SamlIdpServiceImpl implements SamlIdpService { NameID.TRANSIENT, authnRequest.getID(), authnRequest.getAssertionConsumerServiceURL())); } + if (spMetadata.getGenericStore().containsKey("show_consent") + && spMetadata.getGenericStore().get("show_consent").equalsIgnoreCase("true")) { + if (!ReleaseStatusType.GOOD.equals(attributeRelease.getReleaseStatus())) { + // send client to attribute release page + logger.debug("Attribute Release is not good, sending user to constent page"); + return "/user/attribute-release-saml.xhtml?id=" + attributeRelease.getId(); + } + } else { + attributeRelease.setReleaseStatus(null); + } + assertion.getAttributeStatements().add(attributeStatement); + return null; } private List<Object> checkRules(UserEntity user, ServiceEntity service, RegistryEntity registry) { diff --git a/bwreg-webapp/src/main/java/edu/kit/scc/webreg/bean/ar/SamlAttributeReleaseBean.java b/bwreg-webapp/src/main/java/edu/kit/scc/webreg/bean/ar/SamlAttributeReleaseBean.java new file mode 100644 index 0000000000000000000000000000000000000000..dd5be0b3c48e6b60d46a35c5be3a4095b8d476df --- /dev/null +++ b/bwreg-webapp/src/main/java/edu/kit/scc/webreg/bean/ar/SamlAttributeReleaseBean.java @@ -0,0 +1,138 @@ +package edu.kit.scc.webreg.bean.ar; + +import static edu.kit.scc.webreg.dao.ops.PaginateBy.unlimited; +import static edu.kit.scc.webreg.dao.ops.RqlExpressions.equal; +import static edu.kit.scc.webreg.dao.ops.SortBy.ascendingBy; + +import java.io.IOException; +import java.io.Serializable; +import java.util.Arrays; +import java.util.List; + +import edu.kit.scc.webreg.entity.SamlAuthnRequestEntity; +import edu.kit.scc.webreg.entity.SamlAuthnRequestEntity_; +import edu.kit.scc.webreg.entity.attribute.AttributeReleaseEntity; +import edu.kit.scc.webreg.entity.attribute.AttributeReleaseEntity_; +import edu.kit.scc.webreg.entity.attribute.value.StringListValueEntity_; +import edu.kit.scc.webreg.entity.attribute.value.ValueEntity; +import edu.kit.scc.webreg.entity.attribute.value.ValueEntity_; +import edu.kit.scc.webreg.entity.identity.IdentityEntity; +import edu.kit.scc.webreg.entity.oidc.OidcFlowStateEntity; +import edu.kit.scc.webreg.entity.oidc.OidcFlowStateEntity_; +import edu.kit.scc.webreg.service.SamlAuthnRequestService; +import edu.kit.scc.webreg.service.attributes.AttributeReleaseService; +import edu.kit.scc.webreg.service.attributes.ValueService; +import edu.kit.scc.webreg.service.identity.IdentityService; +import edu.kit.scc.webreg.service.oidc.OidcFlowStateService; +import edu.kit.scc.webreg.session.SessionManager; +import jakarta.faces.context.FacesContext; +import jakarta.faces.event.ComponentSystemEvent; +import jakarta.faces.view.ViewScoped; +import jakarta.inject.Inject; +import jakarta.inject.Named; + +@Named +@ViewScoped +public class SamlAttributeReleaseBean implements Serializable { + + private static final long serialVersionUID = 1L; + + @Inject + private SessionManager session; + + @Inject + private IdentityService identityService; + + @Inject + private SamlAuthnRequestService authnRequestService; + + @Inject + private AttributeReleaseService attributeReleaseService; + + @Inject + private ValueService valueService; + + private Long id; + private IdentityEntity identity; + private SamlAuthnRequestEntity authnRequest; + private AttributeReleaseEntity attributeRelease; + private List<ValueEntity> valueList; + + public void preRenderView(ComponentSystemEvent ev) { + if (identity == null) { + if (session.getIdentityId() != null) { + identity = identityService.fetch(session.getIdentityId()); + } + + if (identity == null) { + throw new IllegalStateException("User ID missing."); + } + + if (session.getAuthnRequestId() == null) { + throw new IllegalStateException("There is no AuthnRequest attached to actual session"); + } + authnRequest = authnRequestService.findByIdWithAttrs(session.getAuthnRequestId()); + if (authnRequest == null) { + throw new IllegalStateException("Corresponding AuthnRequest not found."); + } + + attributeRelease = attributeReleaseService.findByIdWithAttrs(authnRequest.getAttributeRelease().getId()); + if (!attributeRelease.getIdentity().equals(identity)) { + throw new IllegalStateException("Not authorised."); + } + + attributeRelease = attributeReleaseService.findByIdWithAttrs(authnRequest.getAttributeRelease().getId(), + AttributeReleaseEntity_.values); + } + } + + public void accept() { + attributeRelease = attributeReleaseService.accept(attributeRelease); + + String red = "/saml/idp/redirect/response"; + try { + FacesContext.getCurrentInstance().getExternalContext().redirect(red); + } catch (IOException e) { + } + } + + public void reject() { + attributeRelease = attributeReleaseService.reject(attributeRelease); + + String red = "/saml/idp/redirect/response"; + try { + FacesContext.getCurrentInstance().getExternalContext().redirect(red); + } catch (IOException e) { + } + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public IdentityEntity getIdentity() { + return identity; + } + + public List<ValueEntity> getValueList() { + if (valueList == null) { + valueList = valueService.findAllEagerly(unlimited(), Arrays.asList(ascendingBy(ValueEntity_.id)), + equal(ValueEntity_.attributeRelease, getAttributeRelease()), + StringListValueEntity_.valueList); + } + return valueList; + } + + public AttributeReleaseEntity getAttributeRelease() { + return attributeRelease; + } + + public SamlAuthnRequestEntity getAuthnRequest() { + return authnRequest; + } + +} diff --git a/bwreg-webapp/src/main/webapp/user/attribute-release-saml.xhtml b/bwreg-webapp/src/main/webapp/user/attribute-release-saml.xhtml new file mode 100644 index 0000000000000000000000000000000000000000..7a4c6d93a9e615491332379bc6e896540a3b778e --- /dev/null +++ b/bwreg-webapp/src/main/webapp/user/attribute-release-saml.xhtml @@ -0,0 +1,80 @@ +<?xml version='1.0' encoding='UTF-8' ?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:f="jakarta.faces.core" + xmlns:h="jakarta.faces.html" + xmlns:ui="jakarta.faces.facelets" + xmlns:bw="http://www.scc.kit.edu/bwfacelets" + xmlns:p="http://primefaces.org/ui" + xmlns:of="http://omnifaces.org/functions"> +<head> +<title></title> +</head> +<body> + +<f:view> +<f:metadata> + <f:viewParam name="id" value="#{samlAttributeReleaseBean.id}"/> + <f:event type="jakarta.faces.event.PreRenderViewEvent" + listener="#{samlAttributeReleaseBean.preRenderView}" /> +</f:metadata> + +<ui:composition template="/template/default.xhtml"> + <ui:param name="title" value="#{messages.title}"/> + + <ui:define name="content"> + <h:form id="form" prependId="false" class="full"> + + <h3><h:outputText value="#{messages['attribute_release.header']}" /></h3> + + <h:outputText value="#{messages['attribute_release.intro']}" /> + <p:panel> + <p:panelGrid id="dataGrid" style="margin-top:1em;" columns="2" layout="flex" columnClasses="col-12 md:col-3 xl:col-3, col-12 md:col-9 xl:col-9"> + <h:outputText value="#{messages['attribute_release.release_to_service']}: " /> + <h:panelGroup> + <b><h:outputText value="#{samlAttributeReleaseBean.authnRequest.spMetadata.displayName}" styleClass="full" rendered="#{samlAttributeReleaseBean.authnRequest.spMetadata.displayName != null}"/></b> + <b><h:outputText value="#{samlAttributeReleaseBean.authnRequest.spMetadata.entityId}" styleClass="full" rendered="#{samlAttributeReleaseBean.authnRequest.spMetadata.displayName == null}"/></b> + </h:panelGroup> + + <h:outputText value="#{messages['attribute_release.actual_release_status']}: " /> + <h:panelGroup styleClass="full"> + <h:outputText value="#{messages['attribute_release.status_new']}" rendered="#{samlAttributeReleaseBean.attributeRelease.releaseStatus == 'NEW'}" /> + <h:outputText value="#{messages['attribute_release.status_dirty']}" rendered="#{samlAttributeReleaseBean.attributeRelease.releaseStatus == 'DIRTY'}" /> + <h:outputText value="#{messages['attribute_release.status_rejected']}" rendered="#{samlAttributeReleaseBean.attributeRelease.releaseStatus == 'REJECTED'}" /> + <h:outputText value="#{messages['attribute_release.status_revoked']}" rendered="#{samlAttributeReleaseBean.attributeRelease.releaseStatus == 'REVOKED'}" /> + </h:panelGroup> + </p:panelGrid> +</p:panel> + <p:dataTable var="value" value="#{samlAttributeReleaseBean.valueList}"> + <p:column width="30%"> + <f:facet name="header"> + <h:outputText value="#{messages.name}" /> + </f:facet> + <h:outputText value="#{value.attribute.name}" /> + </p:column> + <p:column> + <f:facet name="header"> + <h:outputText value="#{messages.value}" /> + </f:facet> + <h:outputText value="#{value.valueString} " rendered="#{value.class.simpleName == 'StringValueEntity'}" /> + <h:outputText value="#{value.valueIdentifier}@#{value.valueScope} " rendered="#{value.class.simpleName == 'PairwiseIdentifierValueEntity'}" /> + <p:outputPanel rendered="#{value.class.simpleName == 'StringListValueEntity'}"> + <ul><ui:repeat value="#{value.valueList}" var="item"> + <li><h:outputText value="#{item}"/></li> + </ui:repeat></ul> + </p:outputPanel> + </p:column> + + </p:dataTable> + + <p:panel> + <p:commandButton action="#{samlAttributeReleaseBean.reject()}" value="#{messages.deny}" rendered="false"/> + <p:commandButton action="#{samlAttributeReleaseBean.accept()}" value="#{messages.accept}" /> + </p:panel> + </h:form> + </ui:define> +</ui:composition> +</f:view> +</body> +</html> diff --git a/bwreg-webapp/src/main/webapp/user/show-attribute-release.xhtml b/bwreg-webapp/src/main/webapp/user/show-attribute-release.xhtml index 27226113de8dea66268024b748a05318d270b8e5..d3c7d33a8925953d8c8de18e2385a18ad6fc4097 100644 --- a/bwreg-webapp/src/main/webapp/user/show-attribute-release.xhtml +++ b/bwreg-webapp/src/main/webapp/user/show-attribute-release.xhtml @@ -24,19 +24,22 @@ <ui:param name="title" value="#{messages.title}"/> <ui:define name="content"> - <h:form id="form" prependId="false" class="full form"> + <h:form id="form" prependId="false" class="full"> <p:messages id="msgbox" /> <p:panel id="arPanel"> <h3><h:outputText value="#{messages['attribute_release.show_ar.heading']}"/></h3> - <div><h:outputText value="#{messages['attribute_release.show_ar.intro']}"/></div> + <h:outputText value="#{messages['attribute_release.show_ar.intro']}"/> <p:panelGrid id="dataGrid" style="margin-top:1em;" columns="2" layout="flex" columnClasses="col-12 md:col-3 xl:col-3, col-12 md:col-9 xl:col-9"> <h:outputText value="#{messages['attribute_release.release_to_service']}: " /> <p:outputPanel rendered="#{showAttributeReleaseBean.release.attributeConsumer.class.simpleName == 'OidcClientConfigurationEntity'}"> - <b><h:outputText value="#{showAttributeReleaseBean.release.attributeConsumer.displayName}" /></b> + <h:panelGroup> + <b><h:outputText value="#{showAttributeReleaseBean.release.attributeConsumer.displayName}" styleClass="full" rendered="#{showAttributeReleaseBean.release.attributeConsumer.displayName != null}"/></b> + <b><h:outputText value="#{showAttributeReleaseBean.release.attributeConsumer.entityId}" styleClass="full" rendered="#{showAttributeReleaseBean.release.attributeConsumer.displayName == null}"/></b> + </h:panelGroup> </p:outputPanel> <p:outputPanel rendered="#{showAttributeReleaseBean.release.attributeConsumer.class.simpleName == 'ProjectOidcClientConfigurationEntity'}"> <b><h:outputText value="#{showAttributeReleaseBean.release.attributeConsumer.displayName}" /></b>