parent
538db29bfe
commit
42cece21b4
|
@ -16,8 +16,14 @@
|
||||||
|
|
||||||
package org.springframework.security.saml2.provider.service.registration;
|
package org.springframework.security.saml2.provider.service.registration;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
|
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
|
||||||
|
|
||||||
|
import org.springframework.security.saml2.core.Saml2X509Credential;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link RelyingPartyRegistration.AssertingPartyDetails} that contains
|
* A {@link RelyingPartyRegistration.AssertingPartyDetails} that contains
|
||||||
* OpenSAML-specific members
|
* OpenSAML-specific members
|
||||||
|
@ -66,12 +72,92 @@ public final class OpenSamlAssertingPartyDetails extends RelyingPartyRegistratio
|
||||||
*/
|
*/
|
||||||
public static final class Builder extends RelyingPartyRegistration.AssertingPartyDetails.Builder {
|
public static final class Builder extends RelyingPartyRegistration.AssertingPartyDetails.Builder {
|
||||||
|
|
||||||
private final EntityDescriptor descriptor;
|
private EntityDescriptor descriptor;
|
||||||
|
|
||||||
private Builder(EntityDescriptor descriptor) {
|
private Builder(EntityDescriptor descriptor) {
|
||||||
this.descriptor = descriptor;
|
this.descriptor = descriptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Builder entityId(String entityId) {
|
||||||
|
return (Builder) super.entityId(entityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Builder wantAuthnRequestsSigned(boolean wantAuthnRequestsSigned) {
|
||||||
|
return (Builder) super.wantAuthnRequestsSigned(wantAuthnRequestsSigned);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Builder signingAlgorithms(Consumer<List<String>> signingMethodAlgorithmsConsumer) {
|
||||||
|
return (Builder) super.signingAlgorithms(signingMethodAlgorithmsConsumer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Builder verificationX509Credentials(Consumer<Collection<Saml2X509Credential>> credentialsConsumer) {
|
||||||
|
return (Builder) super.verificationX509Credentials(credentialsConsumer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Builder encryptionX509Credentials(Consumer<Collection<Saml2X509Credential>> credentialsConsumer) {
|
||||||
|
return (Builder) super.encryptionX509Credentials(credentialsConsumer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Builder singleSignOnServiceLocation(String singleSignOnServiceLocation) {
|
||||||
|
return (Builder) super.singleSignOnServiceLocation(singleSignOnServiceLocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Builder singleSignOnServiceBinding(Saml2MessageBinding singleSignOnServiceBinding) {
|
||||||
|
return (Builder) super.singleSignOnServiceBinding(singleSignOnServiceBinding);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Builder singleLogoutServiceLocation(String singleLogoutServiceLocation) {
|
||||||
|
return (Builder) super.singleLogoutServiceLocation(singleLogoutServiceLocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Builder singleLogoutServiceResponseLocation(String singleLogoutServiceResponseLocation) {
|
||||||
|
return (Builder) super.singleLogoutServiceResponseLocation(singleLogoutServiceResponseLocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Builder singleLogoutServiceBinding(Saml2MessageBinding singleLogoutServiceBinding) {
|
||||||
|
return (Builder) super.singleLogoutServiceBinding(singleLogoutServiceBinding);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build an
|
* Build an
|
||||||
* {@link org.springframework.security.saml2.provider.service.registration.OpenSamlAssertingPartyDetails}
|
* {@link org.springframework.security.saml2.provider.service.registration.OpenSamlAssertingPartyDetails}
|
||||||
|
|
|
@ -1,220 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2002-2023 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.springframework.security.saml2.provider.service.registration;
|
|
||||||
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.security.cert.CertificateException;
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import net.shibboleth.utilities.java.support.xml.ParserPool;
|
|
||||||
import org.opensaml.core.config.ConfigurationService;
|
|
||||||
import org.opensaml.core.xml.XMLObject;
|
|
||||||
import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
|
|
||||||
import org.opensaml.core.xml.io.Unmarshaller;
|
|
||||||
import org.opensaml.saml.common.xml.SAMLConstants;
|
|
||||||
import org.opensaml.saml.ext.saml2alg.SigningMethod;
|
|
||||||
import org.opensaml.saml.saml2.metadata.EntitiesDescriptor;
|
|
||||||
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
|
|
||||||
import org.opensaml.saml.saml2.metadata.Extensions;
|
|
||||||
import org.opensaml.saml.saml2.metadata.IDPSSODescriptor;
|
|
||||||
import org.opensaml.saml.saml2.metadata.KeyDescriptor;
|
|
||||||
import org.opensaml.saml.saml2.metadata.SingleLogoutService;
|
|
||||||
import org.opensaml.saml.saml2.metadata.SingleSignOnService;
|
|
||||||
import org.opensaml.security.credential.UsageType;
|
|
||||||
import org.opensaml.xmlsec.keyinfo.KeyInfoSupport;
|
|
||||||
import org.w3c.dom.Document;
|
|
||||||
import org.w3c.dom.Element;
|
|
||||||
|
|
||||||
import org.springframework.security.saml2.Saml2Exception;
|
|
||||||
import org.springframework.security.saml2.core.OpenSamlInitializationService;
|
|
||||||
import org.springframework.security.saml2.core.Saml2X509Credential;
|
|
||||||
|
|
||||||
class OpenSamlMetadataAssertingPartyDetailsConverter {
|
|
||||||
|
|
||||||
static {
|
|
||||||
OpenSamlInitializationService.initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
private final XMLObjectProviderRegistry registry;
|
|
||||||
|
|
||||||
private final ParserPool parserPool;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a {@link OpenSamlMetadataAssertingPartyDetailsConverter}
|
|
||||||
*/
|
|
||||||
OpenSamlMetadataAssertingPartyDetailsConverter() {
|
|
||||||
this.registry = ConfigurationService.get(XMLObjectProviderRegistry.class);
|
|
||||||
this.parserPool = this.registry.getParserPool();
|
|
||||||
}
|
|
||||||
|
|
||||||
Collection<RelyingPartyRegistration.AssertingPartyDetails.Builder> convert(InputStream inputStream) {
|
|
||||||
List<RelyingPartyRegistration.AssertingPartyDetails.Builder> builders = new ArrayList<>();
|
|
||||||
XMLObject xmlObject = xmlObject(inputStream);
|
|
||||||
if (xmlObject instanceof EntitiesDescriptor) {
|
|
||||||
EntitiesDescriptor descriptors = (EntitiesDescriptor) xmlObject;
|
|
||||||
for (EntityDescriptor descriptor : descriptors.getEntityDescriptors()) {
|
|
||||||
if (descriptor.getIDPSSODescriptor(SAMLConstants.SAML20P_NS) != null) {
|
|
||||||
builders.add(convert(descriptor));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (builders.isEmpty()) {
|
|
||||||
throw new Saml2Exception("Metadata contains no IDPSSODescriptor elements");
|
|
||||||
}
|
|
||||||
return builders;
|
|
||||||
}
|
|
||||||
if (xmlObject instanceof EntityDescriptor) {
|
|
||||||
EntityDescriptor descriptor = (EntityDescriptor) xmlObject;
|
|
||||||
return Arrays.asList(convert(descriptor));
|
|
||||||
}
|
|
||||||
throw new Saml2Exception("Unsupported element of type " + xmlObject.getClass());
|
|
||||||
}
|
|
||||||
|
|
||||||
RelyingPartyRegistration.AssertingPartyDetails.Builder convert(EntityDescriptor descriptor) {
|
|
||||||
IDPSSODescriptor idpssoDescriptor = descriptor.getIDPSSODescriptor(SAMLConstants.SAML20P_NS);
|
|
||||||
if (idpssoDescriptor == null) {
|
|
||||||
throw new Saml2Exception("Metadata response is missing the necessary IDPSSODescriptor element");
|
|
||||||
}
|
|
||||||
List<Saml2X509Credential> verification = new ArrayList<>();
|
|
||||||
List<Saml2X509Credential> encryption = new ArrayList<>();
|
|
||||||
for (KeyDescriptor keyDescriptor : idpssoDescriptor.getKeyDescriptors()) {
|
|
||||||
if (keyDescriptor.getUse().equals(UsageType.SIGNING)) {
|
|
||||||
List<X509Certificate> certificates = certificates(keyDescriptor);
|
|
||||||
for (X509Certificate certificate : certificates) {
|
|
||||||
verification.add(Saml2X509Credential.verification(certificate));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (keyDescriptor.getUse().equals(UsageType.ENCRYPTION)) {
|
|
||||||
List<X509Certificate> certificates = certificates(keyDescriptor);
|
|
||||||
for (X509Certificate certificate : certificates) {
|
|
||||||
encryption.add(Saml2X509Credential.encryption(certificate));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (keyDescriptor.getUse().equals(UsageType.UNSPECIFIED)) {
|
|
||||||
List<X509Certificate> certificates = certificates(keyDescriptor);
|
|
||||||
for (X509Certificate certificate : certificates) {
|
|
||||||
verification.add(Saml2X509Credential.verification(certificate));
|
|
||||||
encryption.add(Saml2X509Credential.encryption(certificate));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (verification.isEmpty()) {
|
|
||||||
throw new Saml2Exception(
|
|
||||||
"Metadata response is missing verification certificates, necessary for verifying SAML assertions");
|
|
||||||
}
|
|
||||||
RelyingPartyRegistration.AssertingPartyDetails.Builder party = OpenSamlAssertingPartyDetails
|
|
||||||
.withEntityDescriptor(descriptor).entityId(descriptor.getEntityID())
|
|
||||||
.wantAuthnRequestsSigned(Boolean.TRUE.equals(idpssoDescriptor.getWantAuthnRequestsSigned()))
|
|
||||||
.verificationX509Credentials((c) -> c.addAll(verification))
|
|
||||||
.encryptionX509Credentials((c) -> c.addAll(encryption));
|
|
||||||
List<SigningMethod> signingMethods = signingMethods(idpssoDescriptor);
|
|
||||||
for (SigningMethod method : signingMethods) {
|
|
||||||
party.signingAlgorithms((algorithms) -> algorithms.add(method.getAlgorithm()));
|
|
||||||
}
|
|
||||||
if (idpssoDescriptor.getSingleSignOnServices().isEmpty()) {
|
|
||||||
throw new Saml2Exception(
|
|
||||||
"Metadata response is missing a SingleSignOnService, necessary for sending AuthnRequests");
|
|
||||||
}
|
|
||||||
for (SingleSignOnService singleSignOnService : idpssoDescriptor.getSingleSignOnServices()) {
|
|
||||||
Saml2MessageBinding binding;
|
|
||||||
if (singleSignOnService.getBinding().equals(Saml2MessageBinding.POST.getUrn())) {
|
|
||||||
binding = Saml2MessageBinding.POST;
|
|
||||||
}
|
|
||||||
else if (singleSignOnService.getBinding().equals(Saml2MessageBinding.REDIRECT.getUrn())) {
|
|
||||||
binding = Saml2MessageBinding.REDIRECT;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
party.singleSignOnServiceLocation(singleSignOnService.getLocation()).singleSignOnServiceBinding(binding);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
for (SingleLogoutService singleLogoutService : idpssoDescriptor.getSingleLogoutServices()) {
|
|
||||||
Saml2MessageBinding binding;
|
|
||||||
if (singleLogoutService.getBinding().equals(Saml2MessageBinding.POST.getUrn())) {
|
|
||||||
binding = Saml2MessageBinding.POST;
|
|
||||||
}
|
|
||||||
else if (singleLogoutService.getBinding().equals(Saml2MessageBinding.REDIRECT.getUrn())) {
|
|
||||||
binding = Saml2MessageBinding.REDIRECT;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
String responseLocation = (singleLogoutService.getResponseLocation() == null)
|
|
||||||
? singleLogoutService.getLocation() : singleLogoutService.getResponseLocation();
|
|
||||||
party.singleLogoutServiceLocation(singleLogoutService.getLocation())
|
|
||||||
.singleLogoutServiceResponseLocation(responseLocation).singleLogoutServiceBinding(binding);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return party;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<X509Certificate> certificates(KeyDescriptor keyDescriptor) {
|
|
||||||
try {
|
|
||||||
return KeyInfoSupport.getCertificates(keyDescriptor.getKeyInfo());
|
|
||||||
}
|
|
||||||
catch (CertificateException ex) {
|
|
||||||
throw new Saml2Exception(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<SigningMethod> signingMethods(IDPSSODescriptor idpssoDescriptor) {
|
|
||||||
Extensions extensions = idpssoDescriptor.getExtensions();
|
|
||||||
List<SigningMethod> result = signingMethods(extensions);
|
|
||||||
if (!result.isEmpty()) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
EntityDescriptor descriptor = (EntityDescriptor) idpssoDescriptor.getParent();
|
|
||||||
extensions = descriptor.getExtensions();
|
|
||||||
return signingMethods(extensions);
|
|
||||||
}
|
|
||||||
|
|
||||||
private XMLObject xmlObject(InputStream inputStream) {
|
|
||||||
Document document = document(inputStream);
|
|
||||||
Element element = document.getDocumentElement();
|
|
||||||
Unmarshaller unmarshaller = this.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 Document document(InputStream inputStream) {
|
|
||||||
try {
|
|
||||||
return this.parserPool.parse(inputStream);
|
|
||||||
}
|
|
||||||
catch (Exception ex) {
|
|
||||||
throw new Saml2Exception(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private <T> List<T> signingMethods(Extensions extensions) {
|
|
||||||
if (extensions != null) {
|
|
||||||
return (List<T>) extensions.getUnknownXMLObjects(SigningMethod.DEFAULT_ELEMENT_NAME);
|
|
||||||
}
|
|
||||||
return new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -17,19 +17,211 @@
|
||||||
package org.springframework.security.saml2.provider.service.registration;
|
package org.springframework.security.saml2.provider.service.registration;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import net.shibboleth.utilities.java.support.xml.ParserPool;
|
||||||
|
import org.opensaml.core.config.ConfigurationService;
|
||||||
|
import org.opensaml.core.xml.XMLObject;
|
||||||
|
import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
|
||||||
|
import org.opensaml.core.xml.io.Unmarshaller;
|
||||||
|
import org.opensaml.saml.common.xml.SAMLConstants;
|
||||||
|
import org.opensaml.saml.ext.saml2alg.SigningMethod;
|
||||||
|
import org.opensaml.saml.saml2.metadata.EntitiesDescriptor;
|
||||||
|
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
|
||||||
|
import org.opensaml.saml.saml2.metadata.Extensions;
|
||||||
|
import org.opensaml.saml.saml2.metadata.IDPSSODescriptor;
|
||||||
|
import org.opensaml.saml.saml2.metadata.KeyDescriptor;
|
||||||
|
import org.opensaml.saml.saml2.metadata.SingleLogoutService;
|
||||||
|
import org.opensaml.saml.saml2.metadata.SingleSignOnService;
|
||||||
|
import org.opensaml.security.credential.UsageType;
|
||||||
|
import org.opensaml.xmlsec.keyinfo.KeyInfoSupport;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
|
||||||
|
import org.springframework.security.saml2.Saml2Exception;
|
||||||
|
import org.springframework.security.saml2.core.OpenSamlInitializationService;
|
||||||
|
import org.springframework.security.saml2.core.Saml2X509Credential;
|
||||||
|
|
||||||
class OpenSamlMetadataRelyingPartyRegistrationConverter {
|
class OpenSamlMetadataRelyingPartyRegistrationConverter {
|
||||||
|
|
||||||
private final OpenSamlMetadataAssertingPartyDetailsConverter converter = new OpenSamlMetadataAssertingPartyDetailsConverter();
|
static {
|
||||||
|
OpenSamlInitializationService.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
Collection<RelyingPartyRegistration.Builder> convert(InputStream source) {
|
private final XMLObjectProviderRegistry registry;
|
||||||
Collection<RelyingPartyRegistration.Builder> builders = new ArrayList<>();
|
|
||||||
for (RelyingPartyRegistration.AssertingPartyDetails.Builder builder : this.converter.convert(source)) {
|
private final ParserPool parserPool;
|
||||||
builders.add(new RelyingPartyRegistration.Builder(builder));
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link OpenSamlMetadataRelyingPartyRegistrationConverter}
|
||||||
|
*/
|
||||||
|
OpenSamlMetadataRelyingPartyRegistrationConverter() {
|
||||||
|
this.registry = ConfigurationService.get(XMLObjectProviderRegistry.class);
|
||||||
|
this.parserPool = this.registry.getParserPool();
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenSamlRelyingPartyRegistration.Builder convert(EntityDescriptor descriptor) {
|
||||||
|
IDPSSODescriptor idpssoDescriptor = descriptor.getIDPSSODescriptor(SAMLConstants.SAML20P_NS);
|
||||||
|
if (idpssoDescriptor == null) {
|
||||||
|
throw new Saml2Exception("Metadata response is missing the necessary IDPSSODescriptor element");
|
||||||
|
}
|
||||||
|
List<Saml2X509Credential> verification = new ArrayList<>();
|
||||||
|
List<Saml2X509Credential> encryption = new ArrayList<>();
|
||||||
|
for (KeyDescriptor keyDescriptor : idpssoDescriptor.getKeyDescriptors()) {
|
||||||
|
if (keyDescriptor.getUse().equals(UsageType.SIGNING)) {
|
||||||
|
List<X509Certificate> certificates = certificates(keyDescriptor);
|
||||||
|
for (X509Certificate certificate : certificates) {
|
||||||
|
verification.add(Saml2X509Credential.verification(certificate));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (keyDescriptor.getUse().equals(UsageType.ENCRYPTION)) {
|
||||||
|
List<X509Certificate> certificates = certificates(keyDescriptor);
|
||||||
|
for (X509Certificate certificate : certificates) {
|
||||||
|
encryption.add(Saml2X509Credential.encryption(certificate));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (keyDescriptor.getUse().equals(UsageType.UNSPECIFIED)) {
|
||||||
|
List<X509Certificate> certificates = certificates(keyDescriptor);
|
||||||
|
for (X509Certificate certificate : certificates) {
|
||||||
|
verification.add(Saml2X509Credential.verification(certificate));
|
||||||
|
encryption.add(Saml2X509Credential.encryption(certificate));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (verification.isEmpty()) {
|
||||||
|
throw new Saml2Exception(
|
||||||
|
"Metadata response is missing verification certificates, necessary for verifying SAML assertions");
|
||||||
|
}
|
||||||
|
OpenSamlRelyingPartyRegistration.Builder builder = OpenSamlRelyingPartyRegistration
|
||||||
|
.withAssertingPartyEntityDescriptor(descriptor)
|
||||||
|
.assertingPartyDetails((party) -> party.entityId(descriptor.getEntityID())
|
||||||
|
.wantAuthnRequestsSigned(Boolean.TRUE.equals(idpssoDescriptor.getWantAuthnRequestsSigned()))
|
||||||
|
.verificationX509Credentials((c) -> c.addAll(verification))
|
||||||
|
.encryptionX509Credentials((c) -> c.addAll(encryption)));
|
||||||
|
|
||||||
|
List<SigningMethod> signingMethods = signingMethods(idpssoDescriptor);
|
||||||
|
for (SigningMethod method : signingMethods) {
|
||||||
|
builder.assertingPartyDetails(
|
||||||
|
(party) -> party.signingAlgorithms((algorithms) -> algorithms.add(method.getAlgorithm())));
|
||||||
|
}
|
||||||
|
if (idpssoDescriptor.getSingleSignOnServices().isEmpty()) {
|
||||||
|
throw new Saml2Exception(
|
||||||
|
"Metadata response is missing a SingleSignOnService, necessary for sending AuthnRequests");
|
||||||
|
}
|
||||||
|
for (SingleSignOnService singleSignOnService : idpssoDescriptor.getSingleSignOnServices()) {
|
||||||
|
Saml2MessageBinding binding;
|
||||||
|
if (singleSignOnService.getBinding().equals(Saml2MessageBinding.POST.getUrn())) {
|
||||||
|
binding = Saml2MessageBinding.POST;
|
||||||
|
}
|
||||||
|
else if (singleSignOnService.getBinding().equals(Saml2MessageBinding.REDIRECT.getUrn())) {
|
||||||
|
binding = Saml2MessageBinding.REDIRECT;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
builder.assertingPartyDetails(
|
||||||
|
(party) -> party.singleSignOnServiceLocation(singleSignOnService.getLocation())
|
||||||
|
.singleSignOnServiceBinding(binding));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for (SingleLogoutService singleLogoutService : idpssoDescriptor.getSingleLogoutServices()) {
|
||||||
|
Saml2MessageBinding binding;
|
||||||
|
if (singleLogoutService.getBinding().equals(Saml2MessageBinding.POST.getUrn())) {
|
||||||
|
binding = Saml2MessageBinding.POST;
|
||||||
|
}
|
||||||
|
else if (singleLogoutService.getBinding().equals(Saml2MessageBinding.REDIRECT.getUrn())) {
|
||||||
|
binding = Saml2MessageBinding.REDIRECT;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String responseLocation = (singleLogoutService.getResponseLocation() == null)
|
||||||
|
? singleLogoutService.getLocation() : singleLogoutService.getResponseLocation();
|
||||||
|
builder.assertingPartyDetails(
|
||||||
|
(party) -> party.singleLogoutServiceLocation(singleLogoutService.getLocation())
|
||||||
|
.singleLogoutServiceResponseLocation(responseLocation).singleLogoutServiceBinding(binding));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
Collection<RelyingPartyRegistration.Builder> convert(InputStream inputStream) {
|
||||||
|
List<RelyingPartyRegistration.Builder> builders = new ArrayList<>();
|
||||||
|
XMLObject xmlObject = xmlObject(inputStream);
|
||||||
|
if (xmlObject instanceof EntitiesDescriptor) {
|
||||||
|
EntitiesDescriptor descriptors = (EntitiesDescriptor) xmlObject;
|
||||||
|
for (EntityDescriptor descriptor : descriptors.getEntityDescriptors()) {
|
||||||
|
if (descriptor.getIDPSSODescriptor(SAMLConstants.SAML20P_NS) != null) {
|
||||||
|
builders.add(convert(descriptor));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (builders.isEmpty()) {
|
||||||
|
throw new Saml2Exception("Metadata contains no IDPSSODescriptor elements");
|
||||||
}
|
}
|
||||||
return builders;
|
return builders;
|
||||||
}
|
}
|
||||||
|
if (xmlObject instanceof EntityDescriptor) {
|
||||||
|
EntityDescriptor descriptor = (EntityDescriptor) xmlObject;
|
||||||
|
return Arrays.asList(convert(descriptor));
|
||||||
|
}
|
||||||
|
throw new Saml2Exception("Unsupported element of type " + xmlObject.getClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<X509Certificate> certificates(KeyDescriptor keyDescriptor) {
|
||||||
|
try {
|
||||||
|
return KeyInfoSupport.getCertificates(keyDescriptor.getKeyInfo());
|
||||||
|
}
|
||||||
|
catch (CertificateException ex) {
|
||||||
|
throw new Saml2Exception(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SigningMethod> signingMethods(IDPSSODescriptor idpssoDescriptor) {
|
||||||
|
Extensions extensions = idpssoDescriptor.getExtensions();
|
||||||
|
List<SigningMethod> result = signingMethods(extensions);
|
||||||
|
if (!result.isEmpty()) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
EntityDescriptor descriptor = (EntityDescriptor) idpssoDescriptor.getParent();
|
||||||
|
extensions = descriptor.getExtensions();
|
||||||
|
return signingMethods(extensions);
|
||||||
|
}
|
||||||
|
|
||||||
|
private XMLObject xmlObject(InputStream inputStream) {
|
||||||
|
Document document = document(inputStream);
|
||||||
|
Element element = document.getDocumentElement();
|
||||||
|
Unmarshaller unmarshaller = this.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 Document document(InputStream inputStream) {
|
||||||
|
try {
|
||||||
|
return this.parserPool.parse(inputStream);
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
throw new Saml2Exception(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> List<T> signingMethods(Extensions extensions) {
|
||||||
|
if (extensions != null) {
|
||||||
|
return (List<T>) extensions.getUnknownXMLObjects(SigningMethod.DEFAULT_ELEMENT_NAME);
|
||||||
|
}
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,172 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.security.saml2.provider.service.registration;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
|
||||||
|
|
||||||
|
import org.springframework.security.saml2.core.Saml2X509Credential;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An OpenSAML implementation of {@link RelyingPartyRegistration} that contains OpenSAML
|
||||||
|
* objects like {@link EntityDescriptor}.
|
||||||
|
*
|
||||||
|
* @author Josh Cummings
|
||||||
|
* @since 6.1
|
||||||
|
*/
|
||||||
|
public final class OpenSamlRelyingPartyRegistration extends RelyingPartyRegistration {
|
||||||
|
|
||||||
|
OpenSamlRelyingPartyRegistration(RelyingPartyRegistration registration) {
|
||||||
|
super(registration.getRegistrationId(), registration.getEntityId(),
|
||||||
|
registration.getAssertionConsumerServiceLocation(), registration.getAssertionConsumerServiceBinding(),
|
||||||
|
registration.getSingleLogoutServiceLocation(), registration.getSingleLogoutServiceResponseLocation(),
|
||||||
|
registration.getSingleLogoutServiceBindings(), registration.getAssertingPartyDetails(),
|
||||||
|
registration.getNameIdFormat(), registration.getDecryptionX509Credentials(),
|
||||||
|
registration.getSigningX509Credentials());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public OpenSamlRelyingPartyRegistration.Builder mutate() {
|
||||||
|
OpenSamlAssertingPartyDetails party = getAssertingPartyDetails();
|
||||||
|
return withAssertingPartyEntityDescriptor(party.getEntityDescriptor()).registrationId(getRegistrationId())
|
||||||
|
.entityId(getEntityId()).signingX509Credentials((c) -> c.addAll(getSigningX509Credentials()))
|
||||||
|
.decryptionX509Credentials((c) -> c.addAll(getDecryptionX509Credentials()))
|
||||||
|
.assertionConsumerServiceLocation(getAssertionConsumerServiceLocation())
|
||||||
|
.assertionConsumerServiceBinding(getAssertionConsumerServiceBinding())
|
||||||
|
.singleLogoutServiceLocation(getSingleLogoutServiceLocation())
|
||||||
|
.singleLogoutServiceResponseLocation(getSingleLogoutServiceResponseLocation())
|
||||||
|
.singleLogoutServiceBindings((c) -> c.addAll(getSingleLogoutServiceBindings()))
|
||||||
|
.nameIdFormat(getNameIdFormat())
|
||||||
|
.assertingPartyDetails((assertingParty) -> ((OpenSamlAssertingPartyDetails.Builder) assertingParty)
|
||||||
|
.entityId(party.getEntityId()).wantAuthnRequestsSigned(party.getWantAuthnRequestsSigned())
|
||||||
|
.signingAlgorithms((algorithms) -> algorithms.addAll(party.getSigningAlgorithms()))
|
||||||
|
.verificationX509Credentials((c) -> c.addAll(party.getVerificationX509Credentials()))
|
||||||
|
.encryptionX509Credentials((c) -> c.addAll(party.getEncryptionX509Credentials()))
|
||||||
|
.singleSignOnServiceLocation(party.getSingleSignOnServiceLocation())
|
||||||
|
.singleSignOnServiceBinding(party.getSingleSignOnServiceBinding())
|
||||||
|
.singleLogoutServiceLocation(party.getSingleLogoutServiceLocation())
|
||||||
|
.singleLogoutServiceResponseLocation(party.getSingleLogoutServiceResponseLocation())
|
||||||
|
.singleLogoutServiceBinding(party.getSingleLogoutServiceBinding()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public OpenSamlAssertingPartyDetails getAssertingPartyDetails() {
|
||||||
|
return (OpenSamlAssertingPartyDetails) super.getAssertingPartyDetails();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a {@link Builder} from an entity descriptor
|
||||||
|
* @param entityDescriptor the asserting party's {@link EntityDescriptor}
|
||||||
|
* @return an {@link Builder}
|
||||||
|
*/
|
||||||
|
public static OpenSamlRelyingPartyRegistration.Builder withAssertingPartyEntityDescriptor(
|
||||||
|
EntityDescriptor entityDescriptor) {
|
||||||
|
return new Builder(entityDescriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An OpenSAML version of
|
||||||
|
* {@link org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration.AssertingPartyDetails.Builder}
|
||||||
|
* that contains the underlying {@link EntityDescriptor}
|
||||||
|
*/
|
||||||
|
public static final class Builder extends RelyingPartyRegistration.Builder {
|
||||||
|
|
||||||
|
private Builder(EntityDescriptor entityDescriptor) {
|
||||||
|
super(entityDescriptor.getEntityID(), OpenSamlAssertingPartyDetails.withEntityDescriptor(entityDescriptor));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Builder registrationId(String id) {
|
||||||
|
return (Builder) super.registrationId(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder entityId(String entityId) {
|
||||||
|
return (Builder) super.entityId(entityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder signingX509Credentials(Consumer<Collection<Saml2X509Credential>> credentialsConsumer) {
|
||||||
|
return (Builder) super.signingX509Credentials(credentialsConsumer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Builder decryptionX509Credentials(Consumer<Collection<Saml2X509Credential>> credentialsConsumer) {
|
||||||
|
return (Builder) super.decryptionX509Credentials(credentialsConsumer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Builder assertionConsumerServiceLocation(String assertionConsumerServiceLocation) {
|
||||||
|
return (Builder) super.assertionConsumerServiceLocation(assertionConsumerServiceLocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Builder assertionConsumerServiceBinding(Saml2MessageBinding assertionConsumerServiceBinding) {
|
||||||
|
return (Builder) super.assertionConsumerServiceBinding(assertionConsumerServiceBinding);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Builder singleLogoutServiceBinding(Saml2MessageBinding singleLogoutServiceBinding) {
|
||||||
|
return singleLogoutServiceBindings((saml2MessageBindings) -> {
|
||||||
|
saml2MessageBindings.clear();
|
||||||
|
saml2MessageBindings.add(singleLogoutServiceBinding);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Builder singleLogoutServiceBindings(Consumer<Collection<Saml2MessageBinding>> bindingsConsumer) {
|
||||||
|
return (Builder) super.singleLogoutServiceBindings(bindingsConsumer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Builder singleLogoutServiceLocation(String singleLogoutServiceLocation) {
|
||||||
|
return (Builder) super.singleLogoutServiceLocation(singleLogoutServiceLocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder singleLogoutServiceResponseLocation(String singleLogoutServiceResponseLocation) {
|
||||||
|
return (Builder) super.singleLogoutServiceResponseLocation(singleLogoutServiceResponseLocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Builder nameIdFormat(String nameIdFormat) {
|
||||||
|
return (Builder) super.nameIdFormat(nameIdFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Builder assertingPartyDetails(Consumer<AssertingPartyDetails.Builder> assertingPartyDetails) {
|
||||||
|
return (Builder) super.assertingPartyDetails(assertingPartyDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build an {@link OpenSamlRelyingPartyRegistration}
|
||||||
|
* {@link org.springframework.security.saml2.provider.service.registration.OpenSamlRelyingPartyRegistration}
|
||||||
|
* @return an {@link OpenSamlRelyingPartyRegistration}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public OpenSamlRelyingPartyRegistration build() {
|
||||||
|
return new OpenSamlRelyingPartyRegistration(super.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -62,13 +62,13 @@ public class OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter
|
||||||
OpenSamlInitializationService.initialize();
|
OpenSamlInitializationService.initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
private final OpenSamlMetadataAssertingPartyDetailsConverter converter;
|
private final OpenSamlMetadataRelyingPartyRegistrationConverter converter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter}
|
* Creates a {@link OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter}
|
||||||
*/
|
*/
|
||||||
public OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter() {
|
public OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter() {
|
||||||
this.converter = new OpenSamlMetadataAssertingPartyDetailsConverter();
|
this.converter = new OpenSamlMetadataRelyingPartyRegistrationConverter();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -89,7 +89,7 @@ public class OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter
|
||||||
@Override
|
@Override
|
||||||
public RelyingPartyRegistration.Builder read(Class<? extends RelyingPartyRegistration.Builder> clazz,
|
public RelyingPartyRegistration.Builder read(Class<? extends RelyingPartyRegistration.Builder> clazz,
|
||||||
HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
|
HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
|
||||||
return new RelyingPartyRegistration.Builder(this.converter.convert(inputMessage.getBody()).iterator().next());
|
return this.converter.convert(inputMessage.getBody()).iterator().next();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -26,7 +26,6 @@ import java.util.function.Consumer;
|
||||||
|
|
||||||
import org.opensaml.xmlsec.signature.support.SignatureConstants;
|
import org.opensaml.xmlsec.signature.support.SignatureConstants;
|
||||||
|
|
||||||
import org.springframework.core.convert.converter.Converter;
|
|
||||||
import org.springframework.security.saml2.core.Saml2X509Credential;
|
import org.springframework.security.saml2.core.Saml2X509Credential;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
|
@ -69,7 +68,7 @@ import org.springframework.util.CollectionUtils;
|
||||||
* @author Josh Cummings
|
* @author Josh Cummings
|
||||||
* @since 5.2
|
* @since 5.2
|
||||||
*/
|
*/
|
||||||
public final class RelyingPartyRegistration {
|
public class RelyingPartyRegistration {
|
||||||
|
|
||||||
private final String registrationId;
|
private final String registrationId;
|
||||||
|
|
||||||
|
@ -93,7 +92,7 @@ public final class RelyingPartyRegistration {
|
||||||
|
|
||||||
private final Collection<Saml2X509Credential> signingX509Credentials;
|
private final Collection<Saml2X509Credential> signingX509Credentials;
|
||||||
|
|
||||||
private RelyingPartyRegistration(String registrationId, String entityId, String assertionConsumerServiceLocation,
|
protected RelyingPartyRegistration(String registrationId, String entityId, String assertionConsumerServiceLocation,
|
||||||
Saml2MessageBinding assertionConsumerServiceBinding, String singleLogoutServiceLocation,
|
Saml2MessageBinding assertionConsumerServiceBinding, String singleLogoutServiceLocation,
|
||||||
String singleLogoutServiceResponseLocation, Collection<Saml2MessageBinding> singleLogoutServiceBindings,
|
String singleLogoutServiceResponseLocation, Collection<Saml2MessageBinding> singleLogoutServiceBindings,
|
||||||
AssertingPartyDetails assertingPartyDetails, String nameIdFormat,
|
AssertingPartyDetails assertingPartyDetails, String nameIdFormat,
|
||||||
|
|
|
@ -1,179 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2002-2022 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.springframework.security.saml2.provider.service.registration;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.security.cert.CertificateFactory;
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.util.Base64;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
|
|
||||||
import org.opensaml.xmlsec.signature.support.SignatureConstants;
|
|
||||||
|
|
||||||
import org.springframework.security.saml2.Saml2Exception;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
|
||||||
|
|
||||||
public class OpenSamlMetadataAssertingPartyDetailsConverterTests {
|
|
||||||
|
|
||||||
private static final String CERTIFICATE = "MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk";
|
|
||||||
|
|
||||||
private static final String ENTITIES_DESCRIPTOR_TEMPLATE = "<md:EntitiesDescriptor xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\">\n%s</md:EntitiesDescriptor>";
|
|
||||||
|
|
||||||
private static final String ENTITY_DESCRIPTOR_TEMPLATE = "<md:EntityDescriptor xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\" "
|
|
||||||
+ "xmlns:alg=\"urn:oasis:names:tc:SAML:metadata:algsupport\" " + "entityID=\"entity-id\" "
|
|
||||||
+ "ID=\"_bf133aac099b99b3d81286e1a341f2d34188043a77fe15bf4bf1487dae9b2ea3\">\n%s"
|
|
||||||
+ "</md:EntityDescriptor>";
|
|
||||||
|
|
||||||
private static final String IDP_SSO_DESCRIPTOR_TEMPLATE = "<md:IDPSSODescriptor protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n"
|
|
||||||
+ "%s\n" + "</md:IDPSSODescriptor>";
|
|
||||||
|
|
||||||
private static final String KEY_DESCRIPTOR_TEMPLATE = "<md:KeyDescriptor %s>\n"
|
|
||||||
+ "<ds:KeyInfo xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\">\n" + "<ds:X509Data>\n"
|
|
||||||
+ "<ds:X509Certificate>" + CERTIFICATE + "</ds:X509Certificate>\n" + "</ds:X509Data>\n" + "</ds:KeyInfo>\n"
|
|
||||||
+ "</md:KeyDescriptor>";
|
|
||||||
|
|
||||||
private static final String EXTENSIONS_TEMPLATE = "<md:Extensions>" + "<alg:SigningMethod Algorithm=\""
|
|
||||||
+ SignatureConstants.ALGO_ID_DIGEST_SHA512 + "\"/>" + "</md:Extensions>";
|
|
||||||
|
|
||||||
private static final String SINGLE_SIGN_ON_SERVICE_TEMPLATE = "<md:SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\" "
|
|
||||||
+ "Location=\"sso-location\"/>";
|
|
||||||
|
|
||||||
private OpenSamlMetadataAssertingPartyDetailsConverter converter;
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
public void setup() {
|
|
||||||
this.converter = new OpenSamlMetadataAssertingPartyDetailsConverter();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void readWhenMissingIDPSSODescriptorThenException() {
|
|
||||||
String payload = String.format(ENTITY_DESCRIPTOR_TEMPLATE, "");
|
|
||||||
InputStream inputStream = new ByteArrayInputStream(payload.getBytes());
|
|
||||||
assertThatExceptionOfType(Saml2Exception.class).isThrownBy(() -> this.converter.convert(inputStream))
|
|
||||||
.withMessageContaining("Metadata response is missing the necessary IDPSSODescriptor element");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void readWhenMissingVerificationKeyThenException() {
|
|
||||||
String payload = String.format(ENTITY_DESCRIPTOR_TEMPLATE, String.format(IDP_SSO_DESCRIPTOR_TEMPLATE, ""));
|
|
||||||
InputStream inputStream = new ByteArrayInputStream(payload.getBytes());
|
|
||||||
assertThatExceptionOfType(Saml2Exception.class).isThrownBy(() -> this.converter.convert(inputStream))
|
|
||||||
.withMessageContaining(
|
|
||||||
"Metadata response is missing verification certificates, necessary for verifying SAML assertions");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void readWhenMissingSingleSignOnServiceThenException() {
|
|
||||||
String payload = String.format(ENTITY_DESCRIPTOR_TEMPLATE,
|
|
||||||
String.format(IDP_SSO_DESCRIPTOR_TEMPLATE, String.format(KEY_DESCRIPTOR_TEMPLATE, "use=\"signing\"")));
|
|
||||||
InputStream inputStream = new ByteArrayInputStream(payload.getBytes());
|
|
||||||
assertThatExceptionOfType(Saml2Exception.class).isThrownBy(() -> this.converter.convert(inputStream))
|
|
||||||
.withMessageContaining(
|
|
||||||
"Metadata response is missing a SingleSignOnService, necessary for sending AuthnRequests");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void readWhenDescriptorFullySpecifiedThenConfigures() throws Exception {
|
|
||||||
String payload = String.format(ENTITY_DESCRIPTOR_TEMPLATE,
|
|
||||||
String.format(IDP_SSO_DESCRIPTOR_TEMPLATE,
|
|
||||||
String.format(KEY_DESCRIPTOR_TEMPLATE, "use=\"signing\"")
|
|
||||||
+ String.format(KEY_DESCRIPTOR_TEMPLATE, "use=\"encryption\"") + EXTENSIONS_TEMPLATE
|
|
||||||
+ String.format(SINGLE_SIGN_ON_SERVICE_TEMPLATE)));
|
|
||||||
InputStream inputStream = new ByteArrayInputStream(payload.getBytes());
|
|
||||||
RelyingPartyRegistration.AssertingPartyDetails details = this.converter.convert(inputStream).iterator().next()
|
|
||||||
.build();
|
|
||||||
assertThat(details.getWantAuthnRequestsSigned()).isFalse();
|
|
||||||
assertThat(details.getSigningAlgorithms()).containsExactly(SignatureConstants.ALGO_ID_DIGEST_SHA512);
|
|
||||||
assertThat(details.getSingleSignOnServiceLocation()).isEqualTo("sso-location");
|
|
||||||
assertThat(details.getSingleSignOnServiceBinding()).isEqualTo(Saml2MessageBinding.REDIRECT);
|
|
||||||
assertThat(details.getEntityId()).isEqualTo("entity-id");
|
|
||||||
assertThat(details.getVerificationX509Credentials()).hasSize(1);
|
|
||||||
assertThat(details.getVerificationX509Credentials().iterator().next().getCertificate())
|
|
||||||
.isEqualTo(x509Certificate(CERTIFICATE));
|
|
||||||
assertThat(details.getEncryptionX509Credentials()).hasSize(1);
|
|
||||||
assertThat(details.getEncryptionX509Credentials().iterator().next().getCertificate())
|
|
||||||
.isEqualTo(x509Certificate(CERTIFICATE));
|
|
||||||
assertThat(details).isInstanceOf(OpenSamlAssertingPartyDetails.class);
|
|
||||||
OpenSamlAssertingPartyDetails openSamlDetails = (OpenSamlAssertingPartyDetails) details;
|
|
||||||
EntityDescriptor entityDescriptor = openSamlDetails.getEntityDescriptor();
|
|
||||||
assertThat(entityDescriptor).isNotNull();
|
|
||||||
assertThat(entityDescriptor.getEntityID()).isEqualTo(details.getEntityId());
|
|
||||||
}
|
|
||||||
|
|
||||||
// gh-9051
|
|
||||||
@Test
|
|
||||||
public void readWhenEntitiesDescriptorThenConfigures() throws Exception {
|
|
||||||
String payload = String.format(ENTITIES_DESCRIPTOR_TEMPLATE,
|
|
||||||
String.format(ENTITY_DESCRIPTOR_TEMPLATE,
|
|
||||||
String.format(IDP_SSO_DESCRIPTOR_TEMPLATE,
|
|
||||||
String.format(KEY_DESCRIPTOR_TEMPLATE, "use=\"signing\"")
|
|
||||||
+ String.format(KEY_DESCRIPTOR_TEMPLATE, "use=\"encryption\"")
|
|
||||||
+ String.format(SINGLE_SIGN_ON_SERVICE_TEMPLATE))));
|
|
||||||
InputStream inputStream = new ByteArrayInputStream(payload.getBytes());
|
|
||||||
RelyingPartyRegistration.AssertingPartyDetails details = this.converter.convert(inputStream).iterator().next()
|
|
||||||
.build();
|
|
||||||
assertThat(details.getWantAuthnRequestsSigned()).isFalse();
|
|
||||||
assertThat(details.getSingleSignOnServiceLocation()).isEqualTo("sso-location");
|
|
||||||
assertThat(details.getSingleSignOnServiceBinding()).isEqualTo(Saml2MessageBinding.REDIRECT);
|
|
||||||
assertThat(details.getEntityId()).isEqualTo("entity-id");
|
|
||||||
assertThat(details.getVerificationX509Credentials()).hasSize(1);
|
|
||||||
assertThat(details.getVerificationX509Credentials().iterator().next().getCertificate())
|
|
||||||
.isEqualTo(x509Certificate(CERTIFICATE));
|
|
||||||
assertThat(details.getEncryptionX509Credentials()).hasSize(1);
|
|
||||||
assertThat(details.getEncryptionX509Credentials().iterator().next().getCertificate())
|
|
||||||
.isEqualTo(x509Certificate(CERTIFICATE));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void readWhenKeyDescriptorHasNoUseThenConfiguresBothKeyTypes() throws Exception {
|
|
||||||
String payload = String.format(ENTITY_DESCRIPTOR_TEMPLATE, String.format(IDP_SSO_DESCRIPTOR_TEMPLATE,
|
|
||||||
String.format(KEY_DESCRIPTOR_TEMPLATE, "") + String.format(SINGLE_SIGN_ON_SERVICE_TEMPLATE)));
|
|
||||||
InputStream inputStream = new ByteArrayInputStream(payload.getBytes());
|
|
||||||
RelyingPartyRegistration.AssertingPartyDetails details = this.converter.convert(inputStream).iterator().next()
|
|
||||||
.build();
|
|
||||||
assertThat(details.getVerificationX509Credentials().iterator().next().getCertificate())
|
|
||||||
.isEqualTo(x509Certificate(CERTIFICATE));
|
|
||||||
assertThat(details.getEncryptionX509Credentials()).hasSize(1);
|
|
||||||
assertThat(details.getEncryptionX509Credentials().iterator().next().getCertificate())
|
|
||||||
.isEqualTo(x509Certificate(CERTIFICATE));
|
|
||||||
}
|
|
||||||
|
|
||||||
X509Certificate x509Certificate(String data) {
|
|
||||||
try {
|
|
||||||
InputStream certificate = new ByteArrayInputStream(Base64.getDecoder().decode(data.getBytes()));
|
|
||||||
return (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(certificate);
|
|
||||||
}
|
|
||||||
catch (Exception ex) {
|
|
||||||
throw new IllegalArgumentException(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// gh-9051
|
|
||||||
@Test
|
|
||||||
public void readWhenUnsupportedElementThenSaml2Exception() {
|
|
||||||
String payload = "<saml2:Assertion xmlns:saml2=\"https://some.endpoint\"/>";
|
|
||||||
InputStream inputStream = new ByteArrayInputStream(payload.getBytes());
|
|
||||||
assertThatExceptionOfType(Saml2Exception.class).isThrownBy(() -> this.converter.convert(inputStream))
|
|
||||||
.withMessage("Unsupported element of type saml2:Assertion");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -21,17 +21,47 @@ import java.io.ByteArrayInputStream;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.cert.CertificateFactory;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.Base64;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
|
||||||
|
import org.opensaml.xmlsec.signature.support.SignatureConstants;
|
||||||
|
|
||||||
import org.springframework.core.io.ClassPathResource;
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
import org.springframework.security.saml2.Saml2Exception;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||||
|
|
||||||
public class OpenSamlMetadataRelyingPartyRegistrationConverterTests {
|
public class OpenSamlMetadataRelyingPartyRegistrationConverterTests {
|
||||||
|
|
||||||
|
private static final String CERTIFICATE = "MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk";
|
||||||
|
|
||||||
|
private static final String ENTITIES_DESCRIPTOR_TEMPLATE = "<md:EntitiesDescriptor xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\">\n%s</md:EntitiesDescriptor>";
|
||||||
|
|
||||||
|
private static final String ENTITY_DESCRIPTOR_TEMPLATE = "<md:EntityDescriptor xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\" "
|
||||||
|
+ "xmlns:alg=\"urn:oasis:names:tc:SAML:metadata:algsupport\" " + "entityID=\"entity-id\" "
|
||||||
|
+ "ID=\"_bf133aac099b99b3d81286e1a341f2d34188043a77fe15bf4bf1487dae9b2ea3\">\n%s"
|
||||||
|
+ "</md:EntityDescriptor>";
|
||||||
|
|
||||||
|
private static final String IDP_SSO_DESCRIPTOR_TEMPLATE = "<md:IDPSSODescriptor protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n"
|
||||||
|
+ "%s\n" + "</md:IDPSSODescriptor>";
|
||||||
|
|
||||||
|
private static final String KEY_DESCRIPTOR_TEMPLATE = "<md:KeyDescriptor %s>\n"
|
||||||
|
+ "<ds:KeyInfo xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\">\n" + "<ds:X509Data>\n"
|
||||||
|
+ "<ds:X509Certificate>" + CERTIFICATE + "</ds:X509Certificate>\n" + "</ds:X509Data>\n" + "</ds:KeyInfo>\n"
|
||||||
|
+ "</md:KeyDescriptor>";
|
||||||
|
|
||||||
|
private static final String EXTENSIONS_TEMPLATE = "<md:Extensions>" + "<alg:SigningMethod Algorithm=\""
|
||||||
|
+ SignatureConstants.ALGO_ID_DIGEST_SHA512 + "\"/>" + "</md:Extensions>";
|
||||||
|
|
||||||
|
private static final String SINGLE_SIGN_ON_SERVICE_TEMPLATE = "<md:SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\" "
|
||||||
|
+ "Location=\"sso-location\"/>";
|
||||||
|
|
||||||
private OpenSamlMetadataRelyingPartyRegistrationConverter converter = new OpenSamlMetadataRelyingPartyRegistrationConverter();
|
private OpenSamlMetadataRelyingPartyRegistrationConverter converter = new OpenSamlMetadataRelyingPartyRegistrationConverter();
|
||||||
|
|
||||||
private String metadata;
|
private String metadata;
|
||||||
|
@ -54,4 +84,116 @@ public class OpenSamlMetadataRelyingPartyRegistrationConverterTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readWhenMissingIDPSSODescriptorThenException() {
|
||||||
|
String payload = String.format(ENTITY_DESCRIPTOR_TEMPLATE, "");
|
||||||
|
InputStream inputStream = new ByteArrayInputStream(payload.getBytes());
|
||||||
|
assertThatExceptionOfType(Saml2Exception.class).isThrownBy(() -> this.converter.convert(inputStream))
|
||||||
|
.withMessageContaining("Metadata response is missing the necessary IDPSSODescriptor element");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readWhenMissingVerificationKeyThenException() {
|
||||||
|
String payload = String.format(ENTITY_DESCRIPTOR_TEMPLATE, String.format(IDP_SSO_DESCRIPTOR_TEMPLATE, ""));
|
||||||
|
InputStream inputStream = new ByteArrayInputStream(payload.getBytes());
|
||||||
|
assertThatExceptionOfType(Saml2Exception.class).isThrownBy(() -> this.converter.convert(inputStream))
|
||||||
|
.withMessageContaining(
|
||||||
|
"Metadata response is missing verification certificates, necessary for verifying SAML assertions");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readWhenMissingSingleSignOnServiceThenException() {
|
||||||
|
String payload = String.format(ENTITY_DESCRIPTOR_TEMPLATE,
|
||||||
|
String.format(IDP_SSO_DESCRIPTOR_TEMPLATE, String.format(KEY_DESCRIPTOR_TEMPLATE, "use=\"signing\"")));
|
||||||
|
InputStream inputStream = new ByteArrayInputStream(payload.getBytes());
|
||||||
|
assertThatExceptionOfType(Saml2Exception.class).isThrownBy(() -> this.converter.convert(inputStream))
|
||||||
|
.withMessageContaining(
|
||||||
|
"Metadata response is missing a SingleSignOnService, necessary for sending AuthnRequests");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readWhenDescriptorFullySpecifiedThenConfigures() throws Exception {
|
||||||
|
String payload = String.format(ENTITY_DESCRIPTOR_TEMPLATE,
|
||||||
|
String.format(IDP_SSO_DESCRIPTOR_TEMPLATE,
|
||||||
|
String.format(KEY_DESCRIPTOR_TEMPLATE, "use=\"signing\"")
|
||||||
|
+ String.format(KEY_DESCRIPTOR_TEMPLATE, "use=\"encryption\"") + EXTENSIONS_TEMPLATE
|
||||||
|
+ String.format(SINGLE_SIGN_ON_SERVICE_TEMPLATE)));
|
||||||
|
InputStream inputStream = new ByteArrayInputStream(payload.getBytes());
|
||||||
|
RelyingPartyRegistration.AssertingPartyDetails details = this.converter.convert(inputStream).iterator().next()
|
||||||
|
.build().getAssertingPartyDetails();
|
||||||
|
assertThat(details.getWantAuthnRequestsSigned()).isFalse();
|
||||||
|
assertThat(details.getSigningAlgorithms()).containsExactly(SignatureConstants.ALGO_ID_DIGEST_SHA512);
|
||||||
|
assertThat(details.getSingleSignOnServiceLocation()).isEqualTo("sso-location");
|
||||||
|
assertThat(details.getSingleSignOnServiceBinding()).isEqualTo(Saml2MessageBinding.REDIRECT);
|
||||||
|
assertThat(details.getEntityId()).isEqualTo("entity-id");
|
||||||
|
assertThat(details.getVerificationX509Credentials()).hasSize(1);
|
||||||
|
assertThat(details.getVerificationX509Credentials().iterator().next().getCertificate())
|
||||||
|
.isEqualTo(x509Certificate(CERTIFICATE));
|
||||||
|
assertThat(details.getEncryptionX509Credentials()).hasSize(1);
|
||||||
|
assertThat(details.getEncryptionX509Credentials().iterator().next().getCertificate())
|
||||||
|
.isEqualTo(x509Certificate(CERTIFICATE));
|
||||||
|
assertThat(details).isInstanceOf(OpenSamlAssertingPartyDetails.class);
|
||||||
|
OpenSamlAssertingPartyDetails openSamlDetails = (OpenSamlAssertingPartyDetails) details;
|
||||||
|
EntityDescriptor entityDescriptor = openSamlDetails.getEntityDescriptor();
|
||||||
|
assertThat(entityDescriptor).isNotNull();
|
||||||
|
assertThat(entityDescriptor.getEntityID()).isEqualTo(details.getEntityId());
|
||||||
|
}
|
||||||
|
|
||||||
|
// gh-9051
|
||||||
|
@Test
|
||||||
|
public void readWhenEntitiesDescriptorThenConfigures() throws Exception {
|
||||||
|
String payload = String.format(ENTITIES_DESCRIPTOR_TEMPLATE,
|
||||||
|
String.format(ENTITY_DESCRIPTOR_TEMPLATE,
|
||||||
|
String.format(IDP_SSO_DESCRIPTOR_TEMPLATE,
|
||||||
|
String.format(KEY_DESCRIPTOR_TEMPLATE, "use=\"signing\"")
|
||||||
|
+ String.format(KEY_DESCRIPTOR_TEMPLATE, "use=\"encryption\"")
|
||||||
|
+ String.format(SINGLE_SIGN_ON_SERVICE_TEMPLATE))));
|
||||||
|
InputStream inputStream = new ByteArrayInputStream(payload.getBytes());
|
||||||
|
RelyingPartyRegistration.AssertingPartyDetails details = this.converter.convert(inputStream).iterator().next()
|
||||||
|
.build().getAssertingPartyDetails();
|
||||||
|
assertThat(details.getWantAuthnRequestsSigned()).isFalse();
|
||||||
|
assertThat(details.getSingleSignOnServiceLocation()).isEqualTo("sso-location");
|
||||||
|
assertThat(details.getSingleSignOnServiceBinding()).isEqualTo(Saml2MessageBinding.REDIRECT);
|
||||||
|
assertThat(details.getEntityId()).isEqualTo("entity-id");
|
||||||
|
assertThat(details.getVerificationX509Credentials()).hasSize(1);
|
||||||
|
assertThat(details.getVerificationX509Credentials().iterator().next().getCertificate())
|
||||||
|
.isEqualTo(x509Certificate(CERTIFICATE));
|
||||||
|
assertThat(details.getEncryptionX509Credentials()).hasSize(1);
|
||||||
|
assertThat(details.getEncryptionX509Credentials().iterator().next().getCertificate())
|
||||||
|
.isEqualTo(x509Certificate(CERTIFICATE));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readWhenKeyDescriptorHasNoUseThenConfiguresBothKeyTypes() throws Exception {
|
||||||
|
String payload = String.format(ENTITY_DESCRIPTOR_TEMPLATE, String.format(IDP_SSO_DESCRIPTOR_TEMPLATE,
|
||||||
|
String.format(KEY_DESCRIPTOR_TEMPLATE, "") + String.format(SINGLE_SIGN_ON_SERVICE_TEMPLATE)));
|
||||||
|
InputStream inputStream = new ByteArrayInputStream(payload.getBytes());
|
||||||
|
RelyingPartyRegistration.AssertingPartyDetails details = this.converter.convert(inputStream).iterator().next()
|
||||||
|
.build().getAssertingPartyDetails();
|
||||||
|
assertThat(details.getVerificationX509Credentials().iterator().next().getCertificate())
|
||||||
|
.isEqualTo(x509Certificate(CERTIFICATE));
|
||||||
|
assertThat(details.getEncryptionX509Credentials()).hasSize(1);
|
||||||
|
assertThat(details.getEncryptionX509Credentials().iterator().next().getCertificate())
|
||||||
|
.isEqualTo(x509Certificate(CERTIFICATE));
|
||||||
|
}
|
||||||
|
|
||||||
|
X509Certificate x509Certificate(String data) {
|
||||||
|
try {
|
||||||
|
InputStream certificate = new ByteArrayInputStream(Base64.getDecoder().decode(data.getBytes()));
|
||||||
|
return (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(certificate);
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
throw new IllegalArgumentException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// gh-9051
|
||||||
|
@Test
|
||||||
|
public void readWhenUnsupportedElementThenSaml2Exception() {
|
||||||
|
String payload = "<saml2:Assertion xmlns:saml2=\"https://some.endpoint\"/>";
|
||||||
|
InputStream inputStream = new ByteArrayInputStream(payload.getBytes());
|
||||||
|
assertThatExceptionOfType(Saml2Exception.class).isThrownBy(() -> this.converter.convert(inputStream))
|
||||||
|
.withMessage("Unsupported element of type saml2:Assertion");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue