Skip to content

Commit

Permalink
Merge pull request #51 from swedenconnect/feature/IS-50-eidas-types
Browse files Browse the repository at this point in the history
IS-50 Support for eIDAS attribute values
  • Loading branch information
martin-lindstrom authored Nov 24, 2023
2 parents 8fd8e34 + dca5d6b commit b6d8c6a
Show file tree
Hide file tree
Showing 16 changed files with 1,100 additions and 41 deletions.
2 changes: 1 addition & 1 deletion autoconfigure/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<parent>
<groupId>se.swedenconnect.spring.saml.idp</groupId>
<artifactId>spring-saml-idp-parent</artifactId>
<version>2.0.1</version>
<version>2.0.2-SNAPSHOT</version>
</parent>

<name>Sweden Connect :: Spring SAML Identity Provider :: Spring Boot Autoconfigure module</name>
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<groupId>se.swedenconnect.spring.saml.idp</groupId>
<artifactId>spring-saml-idp-parent</artifactId>
<packaging>pom</packaging>
<version>2.0.1</version>
<version>2.0.2-SNAPSHOT</version>

<name>Sweden Connect :: Spring SAML Identity Provider :: Parent POM</name>
<description>Parent POM for Spring SAML Identity Provider libraries</description>
Expand Down
2 changes: 1 addition & 1 deletion saml-identity-provider/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<parent>
<groupId>se.swedenconnect.spring.saml.idp</groupId>
<artifactId>spring-saml-idp-parent</artifactId>
<version>2.0.1</version>
<version>2.0.2-SNAPSHOT</version>
</parent>

<name>Sweden Connect :: Spring SAML Identity Provider :: Core Library</name>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public final class Saml2IdentityProviderVersion {

private static final int MAJOR = 2;
private static final int MINOR = 0;
private static final int PATCH = 1;
private static final int PATCH = 2;

/**
* Global serialization value for SAML Identity Provider classes.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package se.swedenconnect.spring.saml.idp.attributes;

import java.io.ByteArrayInputStream;
import java.io.Serializable;
import java.time.Instant;
import java.time.LocalDate;
Expand All @@ -27,19 +28,28 @@
import java.util.stream.Collectors;

import org.opensaml.core.xml.XMLObject;
import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
import org.opensaml.core.xml.io.MarshallingException;
import org.opensaml.core.xml.io.UnmarshallingException;
import org.opensaml.core.xml.schema.XSAny;
import org.opensaml.core.xml.schema.XSBase64Binary;
import org.opensaml.core.xml.schema.XSBoolean;
import org.opensaml.core.xml.schema.XSBooleanValue;
import org.opensaml.core.xml.schema.XSDateTime;
import org.opensaml.core.xml.schema.XSInteger;
import org.opensaml.core.xml.schema.XSString;
import org.opensaml.core.xml.util.XMLObjectSupport;
import org.opensaml.saml.saml2.core.Attribute;
import org.springframework.util.Assert;
import org.w3c.dom.Element;

import net.shibboleth.shared.xml.SerializeSupport;
import net.shibboleth.shared.xml.XMLParserException;
import se.swedenconnect.opensaml.saml2.attribute.AttributeBuilder;
import se.swedenconnect.opensaml.saml2.attribute.AttributeUtils;
import se.swedenconnect.spring.saml.idp.Saml2IdentityProviderVersion;
import se.swedenconnect.spring.saml.idp.attributes.eidas.EidasAttributeValue;
import se.swedenconnect.spring.saml.idp.attributes.eidas.EidasAttributeValueConverter;

/**
* A representation of a user (identity) attribute.
Expand Down Expand Up @@ -68,37 +78,37 @@ public class UserAttribute implements Serializable {

/**
* Constructor.
*
*
* @param id the attribute ID (name)
*/
public UserAttribute(final String id) {
this(id, null, (Serializable)null);
this(id, null, (Serializable) null);
}

/**
* Constructor.
*
*
* @param id the attribute ID (name)
* @param friendlyName the attribute friendly name
*/
public UserAttribute(final String id, final String friendlyName) {
this(id, friendlyName, (Serializable)null);
this(id, friendlyName, (Serializable) null);
}

/**
* Constructor.
*
*
* @param id the attribute ID (name)
* @param friendlyName the attribute friendly name
* @param value the attribute value
*/
public UserAttribute(final String id, final String friendlyName, final Serializable value) {
this(id, friendlyName, Optional.ofNullable(value).map(v -> List.of(v)).orElse(null));
}

/**
* Constructor.
*
*
* @param id the attribute ID (name)
* @param friendlyName the attribute friendly name
* @param values the attribute values
Expand All @@ -111,7 +121,7 @@ public UserAttribute(final String id, final String friendlyName, final List<? ex

/**
* Constructs an {@code UserAttribute} given an OpenSAML {@link Attribute}.
*
*
* @param attribute an OpenSAML {@link Attribute}
*/
public UserAttribute(final Attribute attribute) {
Expand All @@ -124,7 +134,10 @@ public UserAttribute(final Attribute attribute) {
// Assert that all values have the same type and return this type.
final Class<?> valueType = processValueType(attribute.getAttributeValues());

if (XSString.class.isAssignableFrom(valueType) || XSAny.class.isAssignableFrom(valueType)) {
if (EidasAttributeValueConverter.isEidasAttribute(valueType)) {
this.values = EidasAttributeValueConverter.getValues(attribute, valueType);
}
else if (XSString.class.isAssignableFrom(valueType) || XSAny.class.isAssignableFrom(valueType)) {
this.values = AttributeUtils.getAttributeValues(attribute, XSString.class).stream()
.map(XSString::getValue)
.collect(Collectors.toList());
Expand Down Expand Up @@ -152,8 +165,10 @@ else if (XSBase64Binary.class.isAssignableFrom(valueType)) {
.collect(Collectors.toList());
}
else {
throw new IllegalArgumentException(
String.format("Unsupported attribute value type %s for %s", valueType.getSimpleName(), this.id));
this.values = attribute.getAttributeValues().stream()
.map(v -> new UnknownAttributeValue(v))
.collect(Collectors.toList());

}
}
}
Expand All @@ -178,7 +193,7 @@ public String getFriendlyName() {

/**
* Assigns the friendly name.
*
*
* @param friendlyName the friendly name
*/
public void setFriendlyName(final String friendlyName) {
Expand All @@ -196,7 +211,7 @@ public String getNameFormat() {

/**
* Assigns the attribute name format.
*
*
* @param nameFormat the name format
*/
public void setNameFormat(final String nameFormat) {
Expand All @@ -209,12 +224,12 @@ public void setNameFormat(final String nameFormat) {
* @return the attribute value(s)
*/
public List<? extends Serializable> getValues() {
return Optional.ofNullable(this.values).orElseGet(() -> Collections.emptyList());
return Optional.ofNullable(this.values).orElseGet(() -> Collections.emptyList());
}

/**
* Assigns the attribute value.
*
*
* @param value the value
* @see #setValues(List)
*/
Expand All @@ -224,7 +239,7 @@ public void setValue(final Serializable value) {

/**
* Assigns the attribute values.
*
*
* @param values the values
* @see #setValue(Serializable)
*/
Expand All @@ -234,7 +249,7 @@ public void setValues(final List<? extends Serializable> values) {

/**
* Converts this object into an OpenSAML {@link Attribute} object.
*
*
* @return an OpenSAML {@link Attribute}
*/
public Attribute toOpenSamlAttribute() {
Expand Down Expand Up @@ -272,6 +287,14 @@ else if (v instanceof byte[]) {
o.setValue(Base64.getEncoder().encodeToString((byte[]) v));
builder.value(o);
}
else if (v instanceof EidasAttributeValue) {
final XMLObject o = ((EidasAttributeValue<?>) v).createXmlObject();
builder.value(o);
}
else if (v instanceof UnknownAttributeValue) {
final XMLObject o = ((UnknownAttributeValue) v).createXmlObject();
builder.value(o);
}
else {
throw new IllegalArgumentException("Unsupported attribute value - " + v.getClass().getSimpleName());
}
Expand All @@ -289,7 +312,7 @@ public String toString() {
if (this.friendlyName != null) {
sb.append(", (").append(this.friendlyName).append(")");
}
final List<? extends Serializable> v = this.getValues();
final List<? extends Serializable> v = this.getValues();
if (!v.isEmpty()) {
if (v.size() == 1) {
sb.append(", value=").append(v.get(0));
Expand All @@ -300,7 +323,7 @@ public String toString() {
}
return sb.toString();
}

public String valuesToString() {
final StringBuffer sb = new StringBuffer();
final List<? extends Serializable> values = this.getValues();
Expand Down Expand Up @@ -335,4 +358,52 @@ private static Class<?> processValueType(final List<XMLObject> values) throws Il
return type;
}

/**
* Class used to store attribute value types that we don't know how to parse.
*/
public static class UnknownAttributeValue implements Serializable {

private static final long serialVersionUID = Saml2IdentityProviderVersion.SERIAL_VERSION_UID;

/** The encoding of the value object. */
private final String encoding;

/**
* Constructor.
*
* @param value
*/
public UnknownAttributeValue(final XMLObject value) {
try {
final Element element = XMLObjectSupport.marshall(Objects.requireNonNull(value, "value must not be null"));
this.encoding = SerializeSupport.nodeToString(element);
}
catch (final MarshallingException e) {
throw new IllegalArgumentException("Failed to marshall " + value.getElementQName().toString(), e);
}
}

/**
* Creates the {@link XMLObject} given its encoding.
*
* @return an {@link XMLObject}
*/
public XMLObject createXmlObject() {
try {
return XMLObjectSupport.unmarshallFromInputStream(
XMLObjectProviderRegistrySupport.getParserPool(), new ByteArrayInputStream(this.encoding.getBytes()));
}
catch (XMLParserException | UnmarshallingException e) {
throw new SecurityException(e);
}
}

/** {@inheritDoc} */
@Override
public String toString() {
return this.encoding;
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright 2023 Sweden Connect
*
* 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
*
* http://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 se.swedenconnect.spring.saml.idp.attributes.eidas;

import java.io.ByteArrayInputStream;
import java.util.Objects;

import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
import org.opensaml.core.xml.io.MarshallingException;
import org.opensaml.core.xml.io.UnmarshallingException;
import org.opensaml.core.xml.util.XMLObjectSupport;
import org.w3c.dom.Element;

import net.shibboleth.shared.xml.SerializeSupport;
import net.shibboleth.shared.xml.XMLParserException;
import se.swedenconnect.opensaml.eidas.ext.attributes.CurrentAddressType;
import se.swedenconnect.spring.saml.idp.Saml2IdentityProviderVersion;

/**
* {@link CurrentAddressType}.
*
* @author Martin Lindström
*/
public class CurrentAddress implements EidasAttributeValue<CurrentAddressType> {

private static final long serialVersionUID = Saml2IdentityProviderVersion.SERIAL_VERSION_UID;

/** The value. */
private final String value;

/** String representation. */
private final String stringRepr;

/**
* Constructor.
*
* @param value the attribute value object
*/
public CurrentAddress(final CurrentAddressType value) {
try {
final Element element = XMLObjectSupport.marshall(Objects.requireNonNull(value, "value must not be null"));
this.value = SerializeSupport.nodeToString(element);
this.stringRepr = value.toSwedishEidString();
}
catch (final MarshallingException e) {
throw new IllegalArgumentException("Failed to marshall CurrentAddressType", e);
}
}

/** {@inheritDoc} */
@Override
public String getValueAsString() {
return this.stringRepr;
}

/** {@inheritDoc} */
@Override
public CurrentAddressType createXmlObject() {
try {
return (CurrentAddressType) XMLObjectSupport.unmarshallFromInputStream(
XMLObjectProviderRegistrySupport.getParserPool(),
new ByteArrayInputStream(this.value.getBytes()));
}
catch (XMLParserException | UnmarshallingException e) {
throw new SecurityException(e);
}
}

/** {@inheritDoc} */
@Override
public String toString() {
return this.getValueAsString();
}

}
Loading

0 comments on commit b6d8c6a

Please sign in to comment.