diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ConfigurationController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ConfigurationController.java index 5453ed0c4..f71e76cb5 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ConfigurationController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ConfigurationController.java @@ -19,7 +19,6 @@ public class ConfigurationController { @GetMapping(value = "/customAttributes") public ResponseEntity getCustomAttributes() { - System.out.println("WOO!\n" + customPropertiesConfiguration.getOverrides()); return ResponseEntity.ok(customPropertiesConfiguration.getAttributes()); } } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/RelyingPartyOverrideProperty.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/RelyingPartyOverrideProperty.java index f3b332e47..7841ff6c6 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/RelyingPartyOverrideProperty.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/RelyingPartyOverrideProperty.java @@ -15,6 +15,8 @@ public class RelyingPartyOverrideProperty { private String persistValue; private List defaultValues; private Collection persistValues; + private String attributeName; + private String attributeFriendlyName; public String getName() { return name; @@ -80,8 +82,35 @@ public void setPersistValues(Collection persistValues) { this.persistValues = persistValues; } + public String getAttributeName() { + return attributeName; + } + + public void setAttributeName(String attributeName) { + this.attributeName = attributeName; + } + + public String getAttributeFriendlyName() { + return attributeFriendlyName; + } + + public void setAttributeFriendlyName(String attributeFriendlyName) { + this.attributeFriendlyName = attributeFriendlyName; + } + @Override public String toString() { - return "RelyingPartyOverrideProperty{" + "\nname='" + name + '\'' + ", \ndisplayName='" + displayName + '\'' + ", \ndisplayType='" + displayType + '\'' + ", \nhelpText='" + helpText + '\'' + ", \npersistType='" + persistType + '\'' + ", \npersistValue='" + persistValue + '\'' + ", \ndefaultValues=" + defaultValues + ", \npersistValues=" + persistValues + "\n}"; + return "RelyingPartyOverrideProperty{" + + "\nname='" + name + '\'' + + ", \ndisplayName='" + displayName + '\'' + + ", \ndisplayType='" + displayType + '\'' + + ", \nhelpText='" + helpText + '\'' + + ", \npersistType='" + persistType + '\'' + + ", \npersistValue='" + persistValue + '\'' + + ", \ndefaultValues=" + defaultValues + + ", \npersistValues=" + persistValues + + ", \nattributeName='" + attributeName + '\'' + + ", \nattributeFriendlyName='" + attributeFriendlyName + '\'' + + "\n}"; } } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/AttributeUtility.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/AttributeUtility.java index a241acd5b..206f2d51d 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/AttributeUtility.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/AttributeUtility.java @@ -3,12 +3,13 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.AttributeValue; import edu.internet2.tier.shibboleth.admin.ui.domain.XSAny; import edu.internet2.tier.shibboleth.admin.ui.domain.XSBoolean; +import edu.internet2.tier.shibboleth.admin.ui.domain.XSInteger; import edu.internet2.tier.shibboleth.admin.ui.domain.XSString; import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects; import org.opensaml.core.xml.schema.XSBooleanValue; -import org.springframework.beans.factory.annotation.Autowired; import java.util.List; +import java.util.Set; /** * @author Bill Smith (wsmith@unicon.net) @@ -17,43 +18,60 @@ public class AttributeUtility { private OpenSamlObjects openSamlObjects; + private static final String URI = "urn:oasis:names:tc:SAML:2.0:attrname-format:uri"; + public AttributeUtility(OpenSamlObjects openSamlObjects) { this.openSamlObjects = openSamlObjects; } public edu.internet2.tier.shibboleth.admin.ui.domain.Attribute createAttributeWithBooleanValue(String name, String friendlyName, Boolean value) { - edu.internet2.tier.shibboleth.admin.ui.domain.Attribute attribute = ((edu.internet2.tier.shibboleth.admin.ui.domain.AttributeBuilder) openSamlObjects.getBuilderFactory().getBuilder(edu.internet2.tier.shibboleth.admin.ui.domain.Attribute.DEFAULT_ELEMENT_NAME)).buildObject(); - attribute.setName(name); - attribute.setFriendlyName(friendlyName); - attribute.setNameFormat("urn:oasis:names:tc:SAML:2.0:attrname-format:uri"); + edu.internet2.tier.shibboleth.admin.ui.domain.Attribute attribute = createNewAttribute(name, friendlyName); XSBoolean xsBoolean = (XSBoolean) openSamlObjects.getBuilderFactory().getBuilder(XSBoolean.TYPE_NAME).buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSBoolean.TYPE_NAME); xsBoolean.setValue(XSBooleanValue.valueOf(value.toString())); - attribute.getAttributeValues().add(xsBoolean); + return attribute; } - public edu.internet2.tier.shibboleth.admin.ui.domain.Attribute createAttributeWithStringValues(String name, List values) { - edu.internet2.tier.shibboleth.admin.ui.domain.Attribute attribute = ((edu.internet2.tier.shibboleth.admin.ui.domain.AttributeBuilder) openSamlObjects.getBuilderFactory() - .getBuilder(edu.internet2.tier.shibboleth.admin.ui.domain.Attribute.DEFAULT_ELEMENT_NAME)).buildObject(); - attribute.setName(name); - //TODO: Do we need a friendlyName? - //TODO: Do we need a NameFormat? - values.forEach(attributeString -> { + public edu.internet2.tier.shibboleth.admin.ui.domain.Attribute createAttributeWithIntegerValue(String name, String friendlyName, Integer value) { + edu.internet2.tier.shibboleth.admin.ui.domain.Attribute attribute = createNewAttribute(name, friendlyName); + + XSInteger xsInteger = (XSInteger) openSamlObjects.getBuilderFactory().getBuilder(XSInteger.TYPE_NAME).buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSInteger.TYPE_NAME); + xsInteger.setValue(value); + attribute.getAttributeValues().add(xsInteger); + + return attribute; + } + + + public edu.internet2.tier.shibboleth.admin.ui.domain.Attribute createAttributeWithStringValues(String name, String friendlyName, String... values) { + edu.internet2.tier.shibboleth.admin.ui.domain.Attribute attribute = createNewAttribute(name, friendlyName); + + for (String value : values) { XSString xsString = (XSString) openSamlObjects.getBuilderFactory().getBuilder(XSString.TYPE_NAME).buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSString.TYPE_NAME); - xsString.setValue(attributeString); + xsString.setValue(value); attribute.getAttributeValues().add(xsString); - }); + } + return attribute; } + + /* + * Provided for calling with name = MDDCConstants.RELEASE_ATTRIBUTES. + */ + public edu.internet2.tier.shibboleth.admin.ui.domain.Attribute createAttributeWithStringValues(String name, List values) { + return createAttributeWithStringValues(name, null, values); + } + + public edu.internet2.tier.shibboleth.admin.ui.domain.Attribute createAttributeWithStringValues(String name, String friendlyName, List values) { + return createAttributeWithStringValues(name, friendlyName, values.toArray(new String[]{})); + } + public edu.internet2.tier.shibboleth.admin.ui.domain.Attribute createAttributeWithArbitraryValues(String name, String friendlyName, String... values) { - edu.internet2.tier.shibboleth.admin.ui.domain.Attribute attribute = ((edu.internet2.tier.shibboleth.admin.ui.domain.AttributeBuilder) openSamlObjects.getBuilderFactory().getBuilder(edu.internet2.tier.shibboleth.admin.ui.domain.Attribute.DEFAULT_ELEMENT_NAME)).buildObject(); - attribute.setName(name); - attribute.setFriendlyName(friendlyName); - attribute.setNameFormat("urn:oasis:names:tc:SAML:2.0:attrname-format:uri"); + edu.internet2.tier.shibboleth.admin.ui.domain.Attribute attribute = createNewAttribute(name, friendlyName); for (String value : values) { XSAny xsAny = (XSAny) openSamlObjects.getBuilderFactory().getBuilder(XSAny.TYPE_NAME).buildObject(AttributeValue.DEFAULT_ELEMENT_NAME); @@ -68,7 +86,22 @@ public edu.internet2.tier.shibboleth.admin.ui.domain.Attribute createAttributeWi return createAttributeWithArbitraryValues(name, friendlyName, values.toArray(new String[]{})); } - //TODO createAttributeFromSet - // createFromNumber? XSInteger - // + public edu.internet2.tier.shibboleth.admin.ui.domain.Attribute createAttributeWithArbitraryValues(String name, String friendlyName, Set values) { + return createAttributeWithStringValues(name, friendlyName, values.toArray(new String[]{})); + } + + + /* Calling this method with name = MDDCConstants.RELEASE_ATTRIBUTES seems to be a special case. In this case, + * we haven't been setting the friendlyName or nameFormat. Hence the null check. + */ + private edu.internet2.tier.shibboleth.admin.ui.domain.Attribute createNewAttribute(String name, String friendlyName) { + edu.internet2.tier.shibboleth.admin.ui.domain.Attribute attribute = ((edu.internet2.tier.shibboleth.admin.ui.domain.AttributeBuilder) openSamlObjects.getBuilderFactory() + .getBuilder(edu.internet2.tier.shibboleth.admin.ui.domain.Attribute.DEFAULT_ELEMENT_NAME)).buildObject(); + attribute.setName(name); + if (friendlyName != null) { + attribute.setFriendlyName(friendlyName); + attribute.setNameFormat(URI); + } + return attribute; + } } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/ModelRepresentationConversions.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/ModelRepresentationConversions.java index d2fbda436..05f595a2b 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/ModelRepresentationConversions.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/util/ModelRepresentationConversions.java @@ -1,6 +1,7 @@ package edu.internet2.tier.shibboleth.admin.util; import edu.internet2.tier.shibboleth.admin.ui.domain.Attribute; +import edu.internet2.tier.shibboleth.admin.ui.domain.RelyingPartyOverrideProperty; import edu.internet2.tier.shibboleth.admin.ui.domain.XSBoolean; import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.RelyingPartyOverridesRepresentation; import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects; @@ -9,6 +10,8 @@ import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; /** @@ -122,50 +125,71 @@ public static List getAttributeListFromA } public static List getAttributeListFromRelyingPartyOverridesRepresentation - (RelyingPartyOverridesRepresentation relyingPartyOverridesRepresentation) { + (List overridePropertyList, + Map relyingPartyOverridesRepresentation) { List list = new ArrayList<>(); - if (relyingPartyOverridesRepresentation != null) { - if (relyingPartyOverridesRepresentation.isSignAssertion()) { - list.add(ATTRIBUTE_UTILITY.createAttributeWithBooleanValue(MDDCConstants.SIGN_ASSERTIONS, MDDCConstants.SIGN_ASSERTIONS_FN, true)); - } - if (relyingPartyOverridesRepresentation.isDontSignResponse()) { - list.add(ATTRIBUTE_UTILITY.createAttributeWithBooleanValue(MDDCConstants.SIGN_RESPONSES, MDDCConstants.SIGN_RESPONSES_FN, false)); - } - if (relyingPartyOverridesRepresentation.isTurnOffEncryption()) { - list.add(ATTRIBUTE_UTILITY.createAttributeWithBooleanValue(MDDCConstants.ENCRYPT_ASSERTIONS, MDDCConstants.ENCRYPT_ASSERTIONS_FN, false)); - } - if (relyingPartyOverridesRepresentation.isUseSha()) { - list.add(ATTRIBUTE_UTILITY.createAttributeWithArbitraryValues(MDDCConstants.SECURITY_CONFIGURATION, MDDCConstants - .SECURITY_CONFIGURATION_FN, "shibboleth.SecurityConfiguration.SHA1")); - } - if (relyingPartyOverridesRepresentation.isIgnoreAuthenticationMethod()) { - // this is actually going to be wrong, but it will work for the time being. this should be a bitmask value that we calculate - // TODO: fix - list.add(ATTRIBUTE_UTILITY.createAttributeWithArbitraryValues(MDDCConstants.DISALLOWED_FEATURES, MDDCConstants.DISALLOWED_FEATURES_FN, - "0x1")); - } - if (relyingPartyOverridesRepresentation.isOmitNotBefore()) { - list.add(ATTRIBUTE_UTILITY.createAttributeWithBooleanValue(MDDCConstants.INCLUDE_CONDITIONS_NOT_BEFORE, MDDCConstants - .INCLUDE_CONDITIONS_NOT_BEFORE_FN, false)); - } - if (relyingPartyOverridesRepresentation.getResponderId() != null && !"".equals(relyingPartyOverridesRepresentation.getResponderId())) { - list.add(ATTRIBUTE_UTILITY.createAttributeWithArbitraryValues(MDDCConstants.RESPONDER_ID, MDDCConstants.RESPONDER_ID_FN, - relyingPartyOverridesRepresentation.getResponderId())); - } - if (relyingPartyOverridesRepresentation.getNameIdFormats() != null && relyingPartyOverridesRepresentation.getNameIdFormats().size() > 0) { - list.add(ATTRIBUTE_UTILITY.createAttributeWithArbitraryValues(MDDCConstants.NAME_ID_FORMAT_PRECEDENCE, MDDCConstants - .NAME_ID_FORMAT_PRECEDENCE_FN, relyingPartyOverridesRepresentation.getNameIdFormats())); - } - if (relyingPartyOverridesRepresentation.getAuthenticationMethods() != null && relyingPartyOverridesRepresentation.getAuthenticationMethods().size() > 0) { - list.add(ATTRIBUTE_UTILITY.createAttributeWithArbitraryValues(MDDCConstants.DEFAULT_AUTHENTICATION_METHODS, MDDCConstants - .DEFAULT_AUTHENTICATION_METHODS_FN, relyingPartyOverridesRepresentation.getAuthenticationMethods())); - } - if (relyingPartyOverridesRepresentation.isForceAuthn()) { - list.add(ATTRIBUTE_UTILITY.createAttributeWithBooleanValue(MDDCConstants.FORCE_AUTHN, MDDCConstants.FORCE_AUTHN_FN, true)); + for (Map.Entry entry : relyingPartyOverridesRepresentation.entrySet()) { + String key = (String) entry.getKey(); + RelyingPartyOverrideProperty overrideProperty = overridePropertyList.stream().filter(op -> op.getDisplayName().equals(key)).findFirst().get(); + switch (AttributeTypes.valueOf(overrideProperty.getDisplayType())) { + case BOOLEAN: + if (!overrideProperty.getPersistType().equals("boolean")) { + // we must be persisting a string then + list.add(ATTRIBUTE_UTILITY.createAttributeWithStringValues(overrideProperty.getAttributeName(), + overrideProperty.getAttributeFriendlyName(), + (String) entry.getValue()); + } else { + list.add(ATTRIBUTE_UTILITY.createAttributeWithBooleanValue(overrideProperty.getAttributeName(), + overrideProperty.getAttributeFriendlyName(), + Boolean.valueOf((String) entry.getValue())); + } + break; + case INTEGER: + list.add(ATTRIBUTE_UTILITY.createAttributeWithIntegerValue(overrideProperty.getAttributeName(), + overrideProperty.getAttributeFriendlyName(), + Integer.valueOf((String) entry.getValue()))); + break; + case STRING: + list.add(ATTRIBUTE_UTILITY.createAttributeWithStringValues(overrideProperty.getAttributeName(), + overrideProperty.getAttributeFriendlyName(), + (String) entry.getValue())); + break; + case SET: + list.add(ATTRIBUTE_UTILITY.createAttributeWithArbitraryValues(overrideProperty.getAttributeName(), + overrideProperty.getAttributeFriendlyName(), + (Set) entry.getValue())); + break; + case LIST: + list.add(ATTRIBUTE_UTILITY.createAttributeWithArbitraryValues(overrideProperty.getAttributeName(), + overrideProperty.getAttributeFriendlyName(), + (List) entry.getValue())); + break; + default: + throw new UnsupportedOperationException("getAttributeListFromRelyingPartyOverridesRepresentation was called with an unsupported type (" + overrideProperty.getDisplayType() + ")!"); } } return (List) (List) list; } + + + private enum AttributeTypes { + BOOLEAN("boolean"), + INTEGER("integer"), + STRING("string"), + SET("set"), + LIST("list"); + + private final String type; + + AttributeTypes(final String type) { + this.type = type; + } + + @Override + public String toString() { + return type; + } + } } diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index dd0dfb1b4..9a5fbaa87 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -28,7 +28,17 @@ custom: # Custom attributes # The following contains a map of "relying party overrides". -# It is imperative when defining them that the "displayType" and "persistType" are known types. +# The structure of an entry is as follows: +# - name: The name of the entry. used to uniquely identify this entry. +# displayName: This will normally be the label used when displaying this override in the UI +# helpText: This is the help-icon hover-over text +# persistType: Optional. If it is necessary to persist something different than the override's display type, +# set that type here. For example, display a boolean, but persist a string. +# persistValue: Required only when persistType is used. Defines the value to be persisted. +# attributeName: This is the name of the attribute to be used in the xml. This is assumed to be a URI. +# attributeFriendlyName: This is the friendly name associated with the above attributeName. +# +# It is imperative when defining these that the "displayType" and "persistType" are known types. # Typos or unsupported values here will result in that override being skipped! # Supported types are as follows: boolean, integer, string, set, list # Note that "persistType" doesn't have to match "displayType". However, the only unmatching combination currently @@ -39,6 +49,8 @@ custom: displayName: Sign the Assertion displayType: boolean helpText: Sign Assertion + attributeName: + attributeFriendlyName: - name: signResponses displayName: Don't Sign the Response displayType: boolean