diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthentication.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthentication.java index 10205f24ca0..e24f8157b03 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthentication.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthentication.java @@ -109,16 +109,6 @@ public UaaAuthentication(UaaPrincipal uaaPrincipal, this.userAttributes = new HashMap<>(userAttributes); } - public UaaAuthentication(UaaAuthentication existingAuthn, UaaPrincipal principal) { - - this(principal, existingAuthn.getCredentials(), List.copyOf(existingAuthn.getAuthorities()), existingAuthn.getExternalGroups(), - existingAuthn.getUserAttributes(), existingAuthn.getUaaAuthenticationDetails(), existingAuthn.isAuthenticated(), - existingAuthn.getAuthenticatedTime(), existingAuthn.getExpiresAt()); - this.authContextClassRef = existingAuthn.authContextClassRef; - this.authenticationMethods = existingAuthn.authenticationMethods; - this.lastLoginSuccessTime = existingAuthn.lastLoginSuccessTime; - } - @Override public String getName() { // Should we return the ID for the principal name? (No, because the diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml40CompatibleAssertionValidators.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml40CompatibleAssertionValidators.java deleted file mode 100644 index b25c14568e3..00000000000 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSaml40CompatibleAssertionValidators.java +++ /dev/null @@ -1,247 +0,0 @@ -package org.cloudfoundry.identity.uaa.provider.saml; - -import org.opensaml.core.config.ConfigurationService; -import org.opensaml.core.xml.config.XMLObjectProviderRegistry; -import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; -import org.opensaml.saml.common.assertion.ValidationContext; -import org.opensaml.saml.common.assertion.ValidationResult; -import org.opensaml.saml.saml2.assertion.ConditionValidator; -import org.opensaml.saml.saml2.assertion.SAML20AssertionValidator; -import org.opensaml.saml.saml2.assertion.SAML2AssertionValidationParameters; -import org.opensaml.saml.saml2.assertion.StatementValidator; -import org.opensaml.saml.saml2.assertion.SubjectConfirmationValidator; -import org.opensaml.saml.saml2.assertion.impl.AudienceRestrictionConditionValidator; -import org.opensaml.saml.saml2.assertion.impl.BearerSubjectConfirmationValidator; -import org.opensaml.saml.saml2.assertion.impl.DelegationRestrictionConditionValidator; -import org.opensaml.saml.saml2.core.Assertion; -import org.opensaml.saml.saml2.core.AuthnRequest; -import org.opensaml.saml.saml2.core.Condition; -import org.opensaml.saml.saml2.core.OneTimeUse; -import org.opensaml.saml.saml2.core.Response; -import org.opensaml.saml.saml2.core.SubjectConfirmation; -import org.opensaml.saml.saml2.core.SubjectConfirmationData; -import org.opensaml.saml.saml2.core.impl.AuthnRequestUnmarshaller; -import org.opensaml.saml.security.impl.SAMLSignatureProfileValidator; -import org.opensaml.xmlsec.signature.support.SignaturePrevalidator; -import org.opensaml.xmlsec.signature.support.SignatureTrustEngine; -import org.springframework.core.convert.converter.Converter; -import org.springframework.security.saml2.core.Saml2Error; -import org.springframework.security.saml2.core.Saml2ErrorCodes; -import org.springframework.security.saml2.core.Saml2ResponseValidatorResult; -import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest; -import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider; -import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; -import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; -import org.springframework.util.StringUtils; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -import javax.annotation.Nonnull; -import javax.xml.namespace.QName; -import java.io.ByteArrayInputStream; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Consumer; - -/** - * This class contains functions to Validate SAML assertions. It is based on the Spring-Security - * class SAML20AssertionValidators within: - * org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider - *

- * But that class is not compatible with OpenSaml 4.0.x - */ -public class OpenSaml40CompatibleAssertionValidators { - - private static final AuthnRequestUnmarshaller authnRequestUnmarshaller; - private static final Collection conditions = new ArrayList<>(); - private static final Collection subjects = new ArrayList<>(); - private static final Collection statements = new ArrayList<>(); - private static final SignaturePrevalidator validator = new SAMLSignatureProfileValidator(); - private static final SAML20AssertionValidator attributeValidator = new SAML20AssertionValidator(conditions, - subjects, statements, null, null) { - @Nonnull - @Override - protected ValidationResult validateSignature(Assertion token, ValidationContext context) { - return ValidationResult.VALID; - } - }; - - static { - XMLObjectProviderRegistry registry = ConfigurationService.get(XMLObjectProviderRegistry.class); - authnRequestUnmarshaller = (AuthnRequestUnmarshaller) registry.getUnmarshallerFactory() - .getUnmarshaller(AuthnRequest.DEFAULT_ELEMENT_NAME); - } - - static { - conditions.add(new AudienceRestrictionConditionValidator()); - conditions.add(new DelegationRestrictionConditionValidator()); - conditions.add(new ConditionValidator() { - @Nonnull - @Override - public QName getServicedCondition() { - return OneTimeUse.DEFAULT_ELEMENT_NAME; - } - - @Nonnull - @Override - public ValidationResult validate(Condition condition, Assertion assertion, ValidationContext context) { - // applications should validate their own OneTimeUse conditions - return ValidationResult.VALID; - } - }); - subjects.add(new BearerSubjectConfirmationValidator() { - @Override - protected ValidationResult validateAddress(SubjectConfirmation confirmation, Assertion assertion, - ValidationContext context, boolean required) { - // applications should validate their own addresses - gh-7514 - return ValidationResult.VALID; - } - }); - } - - public static Converter createDefaultAssertionValidator() { - - return createDefaultAssertionValidatorWithParameters( - (params) -> params.put(SAML2AssertionValidationParameters.CLOCK_SKEW, Duration.ofMinutes(5))); - } - - public static Converter createDefaultAssertionValidatorWithParameters( - Consumer> validationContextParameters) { - return createAssertionValidator(Saml2ErrorCodes.INVALID_ASSERTION, - (assertionToken) -> OpenSaml40CompatibleAssertionValidators.attributeValidator, - (assertionToken) -> createValidationContext(assertionToken, validationContextParameters)); - } - - private static ValidationContext createValidationContext(OpenSaml4AuthenticationProvider.AssertionToken assertionToken, - Consumer> paramsConsumer) { - Saml2AuthenticationToken token = assertionToken.getToken(); - RelyingPartyRegistration relyingPartyRegistration = token.getRelyingPartyRegistration(); - String audience = relyingPartyRegistration.getEntityId(); - String recipient = relyingPartyRegistration.getAssertionConsumerServiceLocation(); - String assertingPartyEntityId = relyingPartyRegistration.getAssertingPartyDetails().getEntityId(); - Map params = new HashMap<>(); - Assertion assertion = assertionToken.getAssertion(); - if (assertionContainsInResponseTo(assertion)) { - String requestId = getAuthnRequestId(token.getAuthenticationRequest()); - params.put(SAML2AssertionValidationParameters.SC_VALID_IN_RESPONSE_TO, requestId); - } - params.put(SAML2AssertionValidationParameters.COND_VALID_AUDIENCES, Collections.singleton(audience)); - params.put(SAML2AssertionValidationParameters.SC_VALID_RECIPIENTS, Collections.singleton(recipient)); - params.put(SAML2AssertionValidationParameters.VALID_ISSUERS, Collections.singleton(assertingPartyEntityId)); - paramsConsumer.accept(params); - return new ValidationContext(params); - } - - private static boolean assertionContainsInResponseTo(Assertion assertion) { - if (assertion.getSubject() == null) { - return false; - } - for (SubjectConfirmation confirmation : assertion.getSubject().getSubjectConfirmations()) { - SubjectConfirmationData confirmationData = confirmation.getSubjectConfirmationData(); - if (confirmationData == null) { - continue; - } - if (StringUtils.hasText(confirmationData.getInResponseTo())) { - return true; - } - } - return false; - } - - private static String getAuthnRequestId(AbstractSaml2AuthenticationRequest serialized) { - AuthnRequest request = parseRequest(serialized); - if (request == null) { - return null; - } - return request.getID(); - } - - private static AuthnRequest parseRequest(AbstractSaml2AuthenticationRequest request) { - if (request == null) { - return null; - } - String samlRequest = request.getSamlRequest(); - if (!StringUtils.hasText(samlRequest)) { - return null; - } - if (request.getBinding() == Saml2MessageBinding.REDIRECT) { - samlRequest = Saml2Utils.samlInflate(Saml2Utils.samlDecode(samlRequest)); - } else { - samlRequest = new String(Saml2Utils.samlDecode(samlRequest), StandardCharsets.UTF_8); - } - try { - Document document = XMLObjectProviderRegistrySupport.getParserPool() - .parse(new ByteArrayInputStream(samlRequest.getBytes(StandardCharsets.UTF_8))); - Element element = document.getDocumentElement(); - return (AuthnRequest) authnRequestUnmarshaller.unmarshall(element); - } catch (Exception ex) { - String message = "Failed to deserialize associated authentication request [" + ex.getMessage() + "]"; - throw createAuthenticationException(Saml2ErrorCodes.MALFORMED_REQUEST_DATA, message, ex); - } - } - - private static Saml2AuthenticationException createAuthenticationException(String code, String message, - Exception cause) { - return new Saml2AuthenticationException(new Saml2Error(code, message), cause); - } - - private static Converter createAssertionValidator(String errorCode, - Converter validatorConverter, - Converter contextConverter) { - - return (assertionToken) -> { - Assertion assertion = assertionToken.getAssertion(); - SAML20AssertionValidator validator = validatorConverter.convert(assertionToken); - ValidationContext context = contextConverter.convert(assertionToken); - try { - ValidationResult result = validator.validate(assertion, context); - if (result == ValidationResult.VALID) { - return Saml2ResponseValidatorResult.success(); - } - } catch (Exception ex) { - String message = String.format("Invalid assertion [%s] for SAML response [%s]: %s", assertion.getID(), - ((Response) assertion.getParent()).getID(), ex.getMessage()); - return Saml2ResponseValidatorResult.failure(new Saml2Error(errorCode, message)); - } - String message = String.format("Invalid assertion [%s] for SAML response [%s]: %s", assertion.getID(), - ((Response) assertion.getParent()).getID(), context.getValidationFailureMessage()); - return Saml2ResponseValidatorResult.failure(new Saml2Error(errorCode, message)); - }; - } - - static SAML20AssertionValidator createSignatureValidator(SignatureTrustEngine engine) { - return new SAML20AssertionValidator(new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), engine, - validator) { - @Nonnull - @Override - protected ValidationResult validateConditions(Assertion assertion, ValidationContext context) { - return ValidationResult.VALID; - } - - @Nonnull - @Override - protected ValidationResult validateSubjectConfirmation(Assertion assertion, ValidationContext context) { - return ValidationResult.VALID; - } - - @Nonnull - @Override - protected ValidationResult validateStatements(Assertion assertion, ValidationContext context) { - return ValidationResult.VALID; - } - - @Override - protected ValidationResult validateIssuer(Assertion assertion, ValidationContext context) { - return ValidationResult.VALID; - } - }; - - } -} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSamlXmlUtils.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSamlXmlUtils.java new file mode 100644 index 00000000000..978ec9bc20c --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/OpenSamlXmlUtils.java @@ -0,0 +1,58 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import lombok.extern.slf4j.Slf4j; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; +import org.opensaml.core.xml.XMLObject; +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.XSQName; +import org.opensaml.core.xml.schema.XSString; +import org.opensaml.core.xml.schema.XSURI; + +import javax.xml.namespace.QName; +import java.time.Instant; + +@Slf4j +public class OpenSamlXmlUtils { + + private OpenSamlXmlUtils() { + throw new java.lang.UnsupportedOperationException("This is a utility class and cannot be instantiated"); + } + + public static String getStringValue(String key, SamlIdentityProviderDefinition definition, XMLObject xmlObject) { + String value = null; + if (xmlObject instanceof XSString xsString) { + value = xsString.getValue(); + } else if (xmlObject instanceof XSAny xsAny) { + value = xsAny.getTextContent(); + } else if (xmlObject instanceof XSInteger xsInteger) { + Integer i = xsInteger.getValue(); + value = i != null ? i.toString() : null; + } else if (xmlObject instanceof XSBoolean xsBoolean) { + XSBooleanValue b = xsBoolean.getValue(); + value = b != null && b.getValue() != null ? b.getValue().toString() : null; + } else if (xmlObject instanceof XSDateTime xsDateTime) { + Instant d = xsDateTime.getValue(); + value = d != null ? d.toString() : null; + } else if (xmlObject instanceof XSQName xsQName) { + QName name = xsQName.getValue(); + value = name != null ? name.toString() : null; + } else if (xmlObject instanceof XSURI xsUri) { + value = xsUri.getURI(); + } else if (xmlObject instanceof XSBase64Binary xsBase64Binary) { + value = xsBase64Binary.getValue(); + } + + if (value != null) { + log.debug("Found SAML user attribute {} of value {} [zone:{}, origin:{}]", key, value, definition.getZoneId(), definition.getIdpEntityAlias()); + return value; + } else if (xmlObject != null) { + log.debug("SAML user attribute {} at is not of type XSString or other recognizable type, {} [zone:{}, origin:{}]", key, xmlObject.getClass().getName(), definition.getZoneId(), definition.getIdpEntityAlias()); + } + return null; + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2Utils.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2Utils.java index fbca1d2ce7d..32b4298320c 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2Utils.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2Utils.java @@ -39,6 +39,7 @@ public final class Saml2Utils { private Saml2Utils() { + throw new java.lang.UnsupportedOperationException("This is a utility class and cannot be instantiated"); } public static String samlEncode(byte[] b) { diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlAuthenticationFilterConfig.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlAuthenticationFilterConfig.java index 688308c6b9e..c67f4abdff8 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlAuthenticationFilterConfig.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlAuthenticationFilterConfig.java @@ -1,9 +1,11 @@ package org.cloudfoundry.identity.uaa.provider.saml; import org.cloudfoundry.identity.uaa.provider.JdbcIdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMembershipManager; import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManager; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.convert.converter.Converter; @@ -49,16 +51,23 @@ SecurityContextRepository securityContextRepository() { @Bean AuthenticationProvider samlAuthenticationProvider(IdentityZoneManager identityZoneManager, final UaaUserDatabase userDatabase, - final JdbcIdentityProviderProvisioning identityProviderProvisioning) { + final JdbcIdentityProviderProvisioning identityProviderProvisioning, + ScimGroupExternalMembershipManager externalMembershipManager, -// SamlUaaResponseAuthenticationConverter samlResponseAuthenticationConverter = -// new SamlUaaResponseAuthenticationConverter(identityZoneManager, userDatabase, identityProviderProvisioning); -// -// OpenSaml4AuthenticationProvider authProvider = new OpenSaml4AuthenticationProvider(); -// //authProvider.setAssertionValidator(OpenSaml40CompatibleAssertionValidators.createDefaultAssertionValidator()); -// authProvider.setResponseAuthenticationConverter(samlResponseAuthenticationConverter); + ApplicationEventPublisher applicationEventPublisher) { - return new SamlLoginAuthenticationProvider(identityZoneManager, userDatabase, identityProviderProvisioning); + SamlUaaUserManager samlUaaUserManager = new SamlUaaUserManager(userDatabase); + samlUaaUserManager.setApplicationEventPublisher(applicationEventPublisher); + + SamlUaaAuthenticationAttributesConverter attributesConverter = new SamlUaaAuthenticationAttributesConverter(); + SamlUaaAuthenticationAuthoritiesConverter authoritiesConverter = new SamlUaaAuthenticationAuthoritiesConverter(externalMembershipManager); + + SamlUaaResponseAuthenticationConverter samlResponseAuthenticationConverter = + new SamlUaaResponseAuthenticationConverter(identityZoneManager, identityProviderProvisioning, + samlUaaUserManager, attributesConverter, authoritiesConverter); + samlResponseAuthenticationConverter.setApplicationEventPublisher(applicationEventPublisher); + + return new SamlLoginAuthenticationProvider(samlResponseAuthenticationConverter); } @Autowired diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlLoginAuthenticationProvider.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlLoginAuthenticationProvider.java index e6476a32a4d..d55164e5854 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlLoginAuthenticationProvider.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlLoginAuthenticationProvider.java @@ -1,112 +1,47 @@ package org.cloudfoundry.identity.uaa.provider.saml; +import lombok.Value; import lombok.extern.slf4j.Slf4j; import net.shibboleth.utilities.java.support.xml.ParserPool; -import org.apache.commons.lang3.StringUtils; -import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; -import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; -import org.cloudfoundry.identity.uaa.authentication.event.IdentityProviderAuthenticationSuccessEvent; -import org.cloudfoundry.identity.uaa.authentication.manager.ExternalGroupAuthorizationEvent; -import org.cloudfoundry.identity.uaa.authentication.manager.InvitedUserAuthenticatedEvent; -import org.cloudfoundry.identity.uaa.authentication.manager.NewUserAuthenticatedEvent; -import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.provider.IdentityProvider; -import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; -import org.cloudfoundry.identity.uaa.provider.JdbcIdentityProviderProvisioning; -import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.user.UaaUser; -import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; -import org.cloudfoundry.identity.uaa.user.UaaUserPrototype; import org.cloudfoundry.identity.uaa.util.UaaUrlUtils; import org.cloudfoundry.identity.uaa.web.UaaSavedRequestAwareAuthenticationSuccessHandler; -import org.cloudfoundry.identity.uaa.zone.IdentityZone; -import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManager; import org.opensaml.core.config.ConfigurationService; -import org.opensaml.core.xml.XMLObject; import org.opensaml.core.xml.config.XMLObjectProviderRegistry; -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.XSQName; -import org.opensaml.core.xml.schema.XSString; -import org.opensaml.core.xml.schema.XSURI; -import org.opensaml.saml.saml2.core.Assertion; -import org.opensaml.saml.saml2.core.Attribute; -import org.opensaml.saml.saml2.core.AttributeStatement; -import org.opensaml.saml.saml2.core.AuthnRequest; import org.opensaml.saml.saml2.core.Response; -import org.opensaml.saml.saml2.core.impl.AuthnRequestUnmarshaller; import org.opensaml.saml.saml2.core.impl.ResponseUnmarshaller; -import org.springframework.context.ApplicationEvent; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ApplicationEventPublisherAware; -import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.authentication.DisabledException; -import org.springframework.security.authentication.LockedException; -import org.springframework.security.authentication.ProviderNotFoundException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.saml2.Saml2Exception; import org.springframework.security.saml2.core.Saml2Error; -import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest; +import org.springframework.security.saml2.core.Saml2ErrorCodes; import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.w3c.dom.Document; import org.w3c.dom.Element; -import javax.xml.namespace.QName; import java.io.ByteArrayInputStream; import java.nio.charset.StandardCharsets; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -import static java.util.Optional.of; -import static org.cloudfoundry.identity.uaa.constants.OriginKeys.NotANumber; -import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.EMAIL_ATTRIBUTE_NAME; -import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.EMAIL_VERIFIED_ATTRIBUTE_NAME; -import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.FAMILY_NAME_ATTRIBUTE_NAME; -import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.GIVEN_NAME_ATTRIBUTE_NAME; -import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.GROUP_ATTRIBUTE_NAME; -import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.PHONE_NUMBER_ATTRIBUTE_NAME; -import static org.cloudfoundry.identity.uaa.util.UaaHttpRequestUtils.isAcceptedInvitationAuthentication; -import static org.cloudfoundry.identity.uaa.util.UaaStringUtils.retainAllMatches; /** - * SAML Authentication Provider responsible for validating of received SAML messages + * SAML Authentication Provider responsible for validating of received SAML messages and creating authentication tokens. + *

+ * Replace with {@link OpenSaml4AuthenticationProvider} when upgrading to OpenSAML 4.1+ */ @Slf4j -public class SamlLoginAuthenticationProvider implements ApplicationEventPublisherAware, AuthenticationProvider, AuthenticationManager { +@Value +public class SamlLoginAuthenticationProvider implements AuthenticationProvider, AuthenticationManager { - public static final String AUTHENTICATION_CONTEXT_CLASS_REFERENCE = "acr"; - private static final AuthnRequestUnmarshaller authnRequestUnmarshaller; private static final ParserPool parserPool; private static final ResponseUnmarshaller responseUnmarshaller; + private final SamlUaaResponseAuthenticationConverter responseAuthenticationConverter; static { XMLObjectProviderRegistry registry = ConfigurationService.get(XMLObjectProviderRegistry.class); - authnRequestUnmarshaller = (AuthnRequestUnmarshaller) registry.getUnmarshallerFactory() - .getUnmarshaller(AuthnRequest.DEFAULT_ELEMENT_NAME); responseUnmarshaller = (ResponseUnmarshaller) registry.getUnmarshallerFactory() .getUnmarshaller(Response.DEFAULT_ELEMENT_NAME); @@ -114,50 +49,21 @@ public class SamlLoginAuthenticationProvider implements ApplicationEventPublishe parserPool = registry.getParserPool(); } - private final IdentityZoneManager identityZoneManager; - private final UaaUserDatabase userDatabase; - private final IdentityProviderProvisioning identityProviderProvisioning; - // private final ScimGroupExternalMembershipManager externalMembershipManager; - private ApplicationEventPublisher eventPublisher; + public SamlLoginAuthenticationProvider(SamlUaaResponseAuthenticationConverter samlResponseAuthenticationConverter) { + this.responseAuthenticationConverter = samlResponseAuthenticationConverter; + } - public SamlLoginAuthenticationProvider(IdentityZoneManager identityZoneManager, - final UaaUserDatabase userDatabase, - final JdbcIdentityProviderProvisioning identityProviderProvisioning) { - this.identityZoneManager = identityZoneManager; - this.userDatabase = userDatabase; - this.identityProviderProvisioning = identityProviderProvisioning; + private static Saml2AuthenticationException createAuthenticationException(String code, String message, + Exception cause) { + return new Saml2AuthenticationException(new Saml2Error(code, message), cause); } /** * Attempts to authenticate the passed {@link Authentication} object, returning a - * fully populated Authentication object (including granted authorities) + * fully populated UaaAuthentication object (including granted authorities) * if successful. *

- * An AuthenticationManager must honour the following contract concerning - * exceptions: - *

- * Exceptions should be tested for and if applicable thrown in the order expressed - * above (i.e. if an account is disabled or locked, the authentication request is - * immediately rejected and the credentials testing process is not performed). This - * prevents credentials being tested against disabled or locked accounts. * - * @param authentication the authentication request object - * @return a fully authenticated object including credentials. May return - * null if the AuthenticationProvider is unable to support - * authentication of the passed Authentication object. In such a case, - * the next AuthenticationProvider that supports the presented - * Authentication class will be tried. - * @throws AuthenticationException if authentication fails. - *

- * TODO: Move below into configuration of * @see OpenSaml4AuthenticationProvider * https://docs.spring.io/spring-security/reference/5.8/migration/servlet/saml2.html#_use_opensaml_4 */ @@ -165,190 +71,15 @@ public SamlLoginAuthenticationProvider(IdentityZoneManager identityZoneManager, public Authentication authenticate(Authentication authentication) throws AuthenticationException { if (!supports(authentication.getClass())) { - throw new IllegalArgumentException("Only SAMLAuthenticationToken is supported, " + authentication.getClass() + " was attempted"); + throw new IllegalArgumentException("Only Saml2AuthenticationToken is supported, " + authentication.getClass() + " was attempted"); } Saml2AuthenticationToken authenticationToken = (Saml2AuthenticationToken) authentication; String serializedResponse = authenticationToken.getSaml2Response(); Response response = parseResponse(serializedResponse); - List assertions = response.getAssertions(); - - for (Assertion assertion : assertions) { - log.debug("Assertion: " + assertion); - } - - IdentityZone zone = identityZoneManager.getCurrentIdentityZone(); - log.debug(String.format("Initiating SAML authentication in zone '%s' domain '%s'", zone.getId(), zone.getSubdomain())); - RelyingPartyRegistration relyingPartyRegistration = authenticationToken.getRelyingPartyRegistration(); - AbstractSaml2AuthenticationRequest authenticationRequest = authenticationToken.getAuthenticationRequest(); - - String relayState; - if (authenticationRequest != null) { - relayState = authenticationRequest.getRelayState(); - } - - String subjectName = assertions.get(0).getSubject().getNameID().getValue(); - UaaPrincipal initialPrincipal = new UaaPrincipal(NotANumber, subjectName, authenticationToken.getName(), - relyingPartyRegistration.getRegistrationId(), authenticationToken.getName(), zone.getId()); - log.debug("Mapped SAML authentication to IDP with origin '{}' and username '{}'", - relyingPartyRegistration.getRegistrationId(), initialPrincipal.getName()); - - List samlAuthorities = List.copyOf(authenticationToken.getAuthorities()); - - LinkedMultiValueMap customAttributes = new LinkedMultiValueMap<>(); -// for (Map.Entry> entry : userAttributes.entrySet()) { -// if (entry.getKey().startsWith(USER_ATTRIBUTE_PREFIX)) { -// customAttributes.put(entry.getKey().substring(USER_ATTRIBUTE_PREFIX.length()), entry.getValue()); -// } -// } - - Set externalGroups = Set.of(); - boolean authenticated = true; - long authenticatedTime = System.currentTimeMillis(); - long expiresAt = -1; - - UaaAuthentication initialUaaAuthentication = new UaaAuthentication(initialPrincipal, - authenticationToken.getCredentials(), samlAuthorities, externalGroups, customAttributes, null, - authenticated, authenticatedTime, - expiresAt); - - String alias = relyingPartyRegistration.getRegistrationId(); -// String relayState = context.getRelayState(); - boolean addNew; - IdentityProvider idp; - SamlIdentityProviderDefinition samlConfig; - try { - idp = identityProviderProvisioning.retrieveByOrigin(alias, identityZoneManager.getCurrentIdentityZoneId()); - samlConfig = idp.getConfig(); - addNew = samlConfig.isAddShadowUserOnLogin(); - if (!idp.isActive()) { - throw new ProviderNotFoundException("Identity Provider has been disabled by administrator for alias:" + alias); - } - } catch (EmptyResultDataAccessException x) { - throw new ProviderNotFoundException("No SAML identity provider found in zone for alias:" + alias); - } -// - log.debug( - String.format( - "Mapped SAML authentication to IDP with origin '%s' and username '%s'", - idp.getOriginKey(), - initialPrincipal.getName() - ) - ); - - //Collection samlAuthorities = retrieveSamlAuthorities(samlConfig, (SAMLCredential) result.getCredentials()); -// -// Collection authorities = - // Collection samlAuthoritinull; -// SamlIdentityProviderDefinition.ExternalGroupMappingMode groupMappingMode = idp.getConfig().getGroupMappingMode(); -// switch (groupMappingMode) { -// case EXPLICITLY_MAPPED: -// authorities = mapAuthorities(idp.getOriginKey(), samlAuthorities); -// break; -// case AS_SCOPES: -// authorities = new LinkedList<>(samlAuthorities); -// break; -// } -// -// Set filteredExternalGroups = filterSamlAuthorities(samlConfig, samlAuthorities); - initialUaaAuthentication.setAuthenticationMethods(Set.of("ext")); - MultiValueMap userAttributes = retrieveUserAttributes(samlConfig, response); - List acrValues = userAttributes.get(AUTHENTICATION_CONTEXT_CLASS_REFERENCE); - if (acrValues != null) { - initialUaaAuthentication.setAuthContextClassRef(Set.copyOf(acrValues)); - } -// -// if (samlConfig.getAuthnContext() != null) { -// if (Collections.disjoint(userAttributes.get(AUTHENTICATION_CONTEXT_CLASS_REFERENCE), samlConfig.getAuthnContext())) { -// throw new BadCredentialsException("Identity Provider did not authenticate with the requested AuthnContext."); -// } -// } -// - UaaUser user = createIfMissing(initialPrincipal, addNew, samlAuthorities, userAttributes); - UaaPrincipal newPrincipal = new UaaPrincipal(user); - UaaAuthentication newAuthentication = new UaaAuthentication(initialUaaAuthentication, newPrincipal); - - publish(new IdentityProviderAuthenticationSuccessEvent(user, newAuthentication, OriginKeys.SAML, identityZoneManager.getCurrentIdentityZoneId())); -// if (samlConfig.isStoreCustomAttributes()) { -// userDatabase.storeUserInfo(user.getId(), -// new UserInfo() -// .setUserAttributes(resultUaaAuthentication.getUserAttributes()) -// .setRoles(new LinkedList(resultUaaAuthentication.getExternalGroups())) -// ); -// } -// configureRelayRedirect(relayState); -// - return newAuthentication; - } -// -// private void process(Saml2AuthenticationToken token, Response response) { -// String issuer = response.getIssuer().getValue(); -// log.debug(LogMessage.format("Processing SAML response from %s", issuer)); -// boolean responseSigned = response.isSigned(); -// -// OpenSaml4AuthenticationProvider.ResponseToken responseToken = new OpenSaml4AuthenticationProvider.ResponseToken(response, token); -// Saml2ResponseValidatorResult result = this.responseSignatureValidator.convert(responseToken); -// if (responseSigned) { -// this.responseElementsDecrypter.accept(responseToken); -// } -// else if (!response.getEncryptedAssertions().isEmpty()) { -// result = result.concat(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, -// "Did not decrypt response [" + response.getID() + "] since it is not signed")); -// } -// result = result.concat(this.responseValidator.convert(responseToken)); -// boolean allAssertionsSigned = true; -// for (Assertion assertion : response.getAssertions()) { -// OpenSaml4AuthenticationProvider.AssertionToken assertionToken = new OpenSaml4AuthenticationProvider.AssertionToken(assertion, token); -// result = result.concat(this.assertionSignatureValidator.convert(assertionToken)); -// allAssertionsSigned = allAssertionsSigned && assertion.isSigned(); -// if (responseSigned || assertion.isSigned()) { -// this.assertionElementsDecrypter.accept(new OpenSaml4AuthenticationProvider.AssertionToken(assertion, token)); -// } -// result = result.concat(this.assertionValidator.convert(assertionToken)); -// } -// if (!responseSigned && !allAssertionsSigned) { -// String description = "Either the response or one of the assertions is unsigned. " -// + "Please either sign the response or all of the assertions."; -// result = result.concat(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, description)); -// } -// Assertion firstAssertion = CollectionUtils.firstElement(response.getAssertions()); -// if (firstAssertion != null && !hasName(firstAssertion)) { -// Saml2Error error = new Saml2Error(Saml2ErrorCodes.SUBJECT_NOT_FOUND, -// "Assertion [" + firstAssertion.getID() + "] is missing a subject"); -// result = result.concat(error); -// } -// -// if (result.hasErrors()) { -// Collection errors = result.getErrors(); -// if (this.logger.isTraceEnabled()) { -// this.logger.debug("Found " + errors.size() + " validation errors in SAML response [" + response.getID() -// + "]: " + errors); -// } -// else if (this.logger.isDebugEnabled()) { -// this.logger -// .debug("Found " + errors.size() + " validation errors in SAML response [" + response.getID() + "]"); -// } -// Saml2Error first = errors.iterator().next(); -// throw createAuthenticationException(first.getErrorCode(), first.getDescription(), null); -// } -// else { -// if (this.logger.isDebugEnabled()) { -// this.logger.debug("Successfully processed SAML Response [" + response.getID() + "]"); -// } -// } -// } - - private Response parseResponse(String response) throws Saml2Exception, Saml2AuthenticationException { - try { - Document document = parserPool - .parse(new ByteArrayInputStream(response.getBytes(StandardCharsets.UTF_8))); - Element element = document.getDocumentElement(); - return (Response) responseUnmarshaller.unmarshall(element); - } catch (Exception ex) { - // TODO: Add error code - throw new Saml2AuthenticationException(new Saml2Error("TODO", "TODO"), ex); - } + ResponseToken responseToken = new ResponseToken(response, authenticationToken); + return responseAuthenticationConverter.convert(responseToken); } @Override @@ -356,270 +87,16 @@ public boolean supports(Class authentication) { return authentication.equals(Saml2AuthenticationToken.class); } -// @Override -// public void setUserDetails(SAMLUserDetailsService userDetails) { -// super.setUserDetails(userDetails); -// } - - - public void configureRelayRedirect(String relayState) { - //configure relay state - if (UaaUrlUtils.isUrl(relayState)) { - RequestContextHolder.currentRequestAttributes() - .setAttribute( - UaaSavedRequestAwareAuthenticationSuccessHandler.URI_OVERRIDE_ATTRIBUTE, - relayState, - RequestAttributes.SCOPE_REQUEST - ); - } - } - -// protected ExpiringUsernameAuthenticationToken getExpiringUsernameAuthenticationToken(Authentication authentication) { -// return (ExpiringUsernameAuthenticationToken) super.authenticate(authentication); -// } - - protected void publish(ApplicationEvent event) { - if (eventPublisher != null) { - eventPublisher.publishEvent(event); - } - } - - protected Set filterSamlAuthorities(SamlIdentityProviderDefinition definition, Collection samlAuthorities) { - List whiteList = of(definition.getExternalGroupsWhitelist()).orElse(Collections.EMPTY_LIST); - Set authorities = samlAuthorities.stream().map(s -> s.getAuthority()).collect(Collectors.toSet()); - Set result = retainAllMatches(authorities, whiteList); - log.debug(String.format("White listed external SAML groups:'%s'", result)); - return result; - } - -// protected Collection mapAuthorities(String origin, Collection authorities) { -// Collection result = new LinkedList<>(); -// log.debug("Mapping SAML authorities:" + authorities); -// for (GrantedAuthority authority : authorities) { -// String externalGroup = authority.getAuthority(); -// log.debug("Attempting to map external group: " + externalGroup); -// for (ScimGroupExternalMember internalGroup : externalMembershipManager.getExternalGroupMapsByExternalGroup(externalGroup, origin, identityZoneManager.getCurrentIdentityZoneId())) { -// String internalName = internalGroup.getDisplayName(); -// log.debug(String.format("Mapped external: '%s' to internal: '%s'", externalGroup, internalName)); -// result.add(new SimpleGrantedAuthority(internalName)); -// } -// } -// return result; -// } - - private Collection retrieveSamlAuthorities(SamlIdentityProviderDefinition definition, Response response) { - if (definition.getAttributeMappings().get(GROUP_ATTRIBUTE_NAME) != null) { - List groupAttributeNames = getGroupAttributeNames(definition); - - Collection authorities = new ArrayList<>(); -// response.getAssertions().stream() -// .filter(attribute -> groupAttributeNames.contains(attribute.getName()) || groupAttributeNames.contains(attribute.getFriendlyName())) -// .filter(attribute -> attribute.getAttributeValues() != null) -// .filter(attribute -> attribute.getAttributeValues().size() > 0) -// .forEach(attribute -> { -// for (XMLObject group : attribute.getAttributeValues()) { -// authorities.add(new SamlUserAuthority(getStringValue(attribute.getName(), -// definition, -// group))); -// } -// }); - - return authorities; - } - return new ArrayList<>(); - } - - private List getGroupAttributeNames(SamlIdentityProviderDefinition definition) { - List attributeNames = new LinkedList<>(); - - if (definition.getAttributeMappings().get(GROUP_ATTRIBUTE_NAME) instanceof String value) { - attributeNames.add(value); - } else if (definition.getAttributeMappings().get(GROUP_ATTRIBUTE_NAME) instanceof Collection value) { - attributeNames.addAll(value); - } - return attributeNames; - } - - public MultiValueMap retrieveUserAttributes(SamlIdentityProviderDefinition definition, Response response) { - log.debug(String.format("Retrieving SAML user attributes [zone:%s, origin:%s]", definition.getZoneId(), definition.getIdpEntityAlias())); - MultiValueMap userAttributes = new LinkedMultiValueMap<>(); - List assertions = response.getAssertions(); - if (assertions.isEmpty()) { - return userAttributes; - } - for (Assertion assertion : assertions) { - if (assertion.getAttributeStatements() != null) { - for (AttributeStatement statement : assertion.getAttributeStatements()) { - for (Attribute attribute : statement.getAttributes()) { - if (attribute.getAttributeValues() != null) { - for (XMLObject xmlObject : attribute.getAttributeValues()) { - String key = attribute.getName(); - String value = getStringValue(key, definition, xmlObject); - if (value != null) { - userAttributes.add(key, value); - } - } - } - } - } - } - } - - if (definition != null && definition.getAttributeMappings() != null) { - for (Map.Entry attributeMapping : definition.getAttributeMappings().entrySet()) { - Object attributeKey = attributeMapping.getValue(); - if (attributeKey instanceof String) { - if (userAttributes.get(attributeKey) != null) { - String key = attributeMapping.getKey(); - userAttributes.addAll(key, userAttributes.get(attributeKey)); - } - } - } - } -// if (credential.getAuthenticationAssertion() != null && credential.getAuthenticationAssertion().getAuthnStatements() != null) { -// for (AuthnStatement statement : credential.getAuthenticationAssertion().getAuthnStatements()) { -// if (statement.getAuthnContext() != null && statement.getAuthnContext().getAuthnContextClassRef() != null) { -// userAttributes.add(AUTHENTICATION_CONTEXT_CLASS_REFERENCE, statement.getAuthnContext().getAuthnContextClassRef().getAuthnContextClassRef()); -// } -// } -// } - return userAttributes; - } - - protected String getStringValue(String key, SamlIdentityProviderDefinition definition, XMLObject xmlObject) { - String value = null; - if (xmlObject instanceof XSString) { - value = ((XSString) xmlObject).getValue(); - } else if (xmlObject instanceof XSAny) { - value = ((XSAny) xmlObject).getTextContent(); - } else if (xmlObject instanceof XSInteger) { - Integer i = ((XSInteger) xmlObject).getValue(); - value = i != null ? i.toString() : null; - } else if (xmlObject instanceof XSBoolean) { - XSBooleanValue b = ((XSBoolean) xmlObject).getValue(); - value = b != null && b.getValue() != null ? b.getValue().toString() : null; - } else if (xmlObject instanceof XSDateTime) { - Instant d = ((XSDateTime) xmlObject).getValue(); - value = d != null ? d.toString() : null; - } else if (xmlObject instanceof XSQName) { - QName name = ((XSQName) xmlObject).getValue(); - value = name != null ? name.toString() : null; - } else if (xmlObject instanceof XSURI) { - value = ((XSURI) xmlObject).getURI(); - } else if (xmlObject instanceof XSBase64Binary) { - value = ((XSBase64Binary) xmlObject).getValue(); - } - - if (value != null) { - log.debug(String.format("Found SAML user attribute %s of value %s [zone:%s, origin:%s]", key, value, definition.getZoneId(), definition.getIdpEntityAlias())); - return value; - } else if (xmlObject != null) { - log.debug(String.format("SAML user attribute %s at is not of type XSString or other recognizable type, %s [zone:%s, origin:%s]", key, xmlObject.getClass().getName(), definition.getZoneId(), definition.getIdpEntityAlias())); - } - return null; - } - - protected UaaUser createIfMissing(UaaPrincipal samlPrincipal, boolean addNew, Collection authorities, MultiValueMap userAttributes) { - UaaUser user = null; - String invitedUserId = null; - boolean is_invitation_acceptance = isAcceptedInvitationAuthentication(); - if (is_invitation_acceptance) { - invitedUserId = (String) RequestContextHolder.currentRequestAttributes().getAttribute("user_id", RequestAttributes.SCOPE_SESSION); - user = userDatabase.retrieveUserById(invitedUserId); - if (userAttributes.getFirst(EMAIL_ATTRIBUTE_NAME) != null) { - if (!userAttributes.getFirst(EMAIL_ATTRIBUTE_NAME).equalsIgnoreCase(user.getEmail())) { - throw new BadCredentialsException("SAML User email mismatch. Authenticated email doesn't match invited email."); - } - } else { - userAttributes = new LinkedMultiValueMap<>(userAttributes); - userAttributes.add(EMAIL_ATTRIBUTE_NAME, user.getEmail()); - } - addNew = false; - if (user.getUsername().equals(user.getEmail()) && !user.getUsername().equals(samlPrincipal.getName())) { - user = user.modifyUsername(samlPrincipal.getName()); - } - publish(new InvitedUserAuthenticatedEvent(user)); - user = userDatabase.retrieveUserById(invitedUserId); - } - - boolean userModified = false; - UaaUser userWithSamlAttributes = getUser(samlPrincipal, userAttributes); + private Response parseResponse(String response) throws Saml2Exception, Saml2AuthenticationException { try { - if (user == null) { - user = userDatabase.retrieveUserByName(samlPrincipal.getName(), samlPrincipal.getOrigin()); - } - } catch (UsernameNotFoundException e) { - UaaUserPrototype uaaUser = userDatabase.retrieveUserPrototypeByEmail(userWithSamlAttributes.getEmail(), samlPrincipal.getOrigin()); - if (uaaUser != null) { - userModified = true; - user = new UaaUser(uaaUser.withUsername(samlPrincipal.getName())); - } else { - if (!addNew) { - throw new SamlLoginException("SAML user does not exist. " - + "You can correct this by creating a shadow user for the SAML user.", e); - } - publish(new NewUserAuthenticatedEvent(userWithSamlAttributes)); - try { - user = new UaaUser(userDatabase.retrieveUserPrototypeByName(samlPrincipal.getName(), samlPrincipal.getOrigin())); - } catch (UsernameNotFoundException ex) { - throw new BadCredentialsException("Unable to establish shadow user for SAML user:" + samlPrincipal.getName()); - } - } - } - if (haveUserAttributesChanged(user, userWithSamlAttributes)) { - userModified = true; - user = user.modifyAttributes(userWithSamlAttributes.getEmail(), - userWithSamlAttributes.getGivenName(), - userWithSamlAttributes.getFamilyName(), - userWithSamlAttributes.getPhoneNumber(), - userWithSamlAttributes.getExternalId(), - user.isVerified() || userWithSamlAttributes.isVerified()); - } - publish( - new ExternalGroupAuthorizationEvent( - user, - userModified, - authorities, - true - ) - ); - user = userDatabase.retrieveUserById(user.getId()); - return user; - } - - protected UaaUser getUser(UaaPrincipal principal, MultiValueMap userAttributes) { - if (principal.getName() == null && userAttributes.getFirst(EMAIL_ATTRIBUTE_NAME) == null) { - throw new BadCredentialsException("Cannot determine username from credentials supplied"); + Document document = parserPool.parse(new ByteArrayInputStream(response.getBytes(StandardCharsets.UTF_8))); + Element element = document.getDocumentElement(); + return (Response) responseUnmarshaller.unmarshall(element); + } catch (Exception ex) { + throw createAuthenticationException(Saml2ErrorCodes.MALFORMED_RESPONSE_DATA, ex.getMessage(), ex); } - - String name = principal.getName(); - return UaaUser.createWithDefaults(u -> - u.withId(OriginKeys.NotANumber) - .withUsername(name) - .withEmail(userAttributes.getFirst(EMAIL_ATTRIBUTE_NAME)) - .withPhoneNumber(userAttributes.getFirst(PHONE_NUMBER_ATTRIBUTE_NAME)) - .withPassword("") - .withGivenName(userAttributes.getFirst(GIVEN_NAME_ATTRIBUTE_NAME)) - .withFamilyName(userAttributes.getFirst(FAMILY_NAME_ATTRIBUTE_NAME)) - .withAuthorities(Collections.emptyList()) - .withVerified(Boolean.valueOf(userAttributes.getFirst(EMAIL_VERIFIED_ATTRIBUTE_NAME))) - .withOrigin(principal.getOrigin() != null ? principal.getOrigin() : OriginKeys.LOGIN_SERVER) - .withExternalId(name) - .withZoneId(principal.getZoneId()) - ); - } - - protected boolean haveUserAttributesChanged(UaaUser existingUser, UaaUser user) { - return existingUser.isVerified() != user.isVerified() || - !StringUtils.equals(existingUser.getGivenName(), user.getGivenName()) || - !StringUtils.equals(existingUser.getFamilyName(), user.getFamilyName()) || - !StringUtils.equals(existingUser.getPhoneNumber(), user.getPhoneNumber()) || - !StringUtils.equals(existingUser.getEmail(), user.getEmail()) || - !StringUtils.equals(existingUser.getExternalId(), user.getExternalId()); } - @Override - public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { - this.eventPublisher = applicationEventPublisher; + public record ResponseToken(Response response, Saml2AuthenticationToken token) { } -} \ No newline at end of file +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlUaaAuthenticationAttributesConverter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlUaaAuthenticationAttributesConverter.java new file mode 100644 index 00000000000..31198c4d09f --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlUaaAuthenticationAttributesConverter.java @@ -0,0 +1,70 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; +import org.opensaml.saml.saml2.core.Assertion; +import org.opensaml.saml.saml2.core.Response; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +import java.util.List; +import java.util.Map; + +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.USER_ATTRIBUTE_PREFIX; + +/** + * Part of the AuthenticationConverter used during SAML login flow. + * This handles the conversion of SAML Attributes to User Attributes + */ +@Slf4j +public class SamlUaaAuthenticationAttributesConverter { + + public MultiValueMap retrieveUserAttributes(SamlIdentityProviderDefinition definition, Response response) { + log.debug("Retrieving SAML user attributes [zone:{}, origin:{}}]", definition.getZoneId(), definition.getIdpEntityAlias()); + MultiValueMap userAttributes = new LinkedMultiValueMap<>(); + List assertions = response.getAssertions(); + if (assertions.isEmpty()) { + return userAttributes; + } + + assertions.stream().flatMap(assertion -> assertion.getAttributeStatements().stream()) + .flatMap(statement -> statement.getAttributes().stream()) + .forEach(attribute -> { + String key = attribute.getName(); + attribute.getAttributeValues().forEach(xmlObject -> { + String value = OpenSamlXmlUtils.getStringValue(key, definition, xmlObject); + if (value != null) { + userAttributes.add(key, value); + } + }); + }); + + if (definition != null && definition.getAttributeMappings() != null) { + definition.getAttributeMappings().forEach((key, attributeKey) -> { + if (attributeKey instanceof String && userAttributes.get(attributeKey) != null) { + userAttributes.addAll(key, userAttributes.get(attributeKey)); + } + }); + } + +// if (credential.getAuthenticationAssertion() != null && credential.getAuthenticationAssertion().getAuthnStatements() != null) { +// for (AuthnStatement statement : credential.getAuthenticationAssertion().getAuthnStatements()) { +// if (statement.getAuthnContext() != null && statement.getAuthnContext().getAuthnContextClassRef() != null) { +// userAttributes.add(AUTHENTICATION_CONTEXT_CLASS_REFERENCE, statement.getAuthnContext().getAuthnContextClassRef().getAuthnContextClassRef()); +// } +// } +// } + return userAttributes; + } + + public MultiValueMap retrieveCustomUserAttributes(MultiValueMap userAttributes) { + MultiValueMap customAttributes = new LinkedMultiValueMap<>(); + for (Map.Entry> entry : userAttributes.entrySet()) { + if (entry.getKey().startsWith(USER_ATTRIBUTE_PREFIX)) { + customAttributes.put(entry.getKey().substring(USER_ATTRIBUTE_PREFIX.length()), entry.getValue()); + } + } + return customAttributes; + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlUaaAuthenticationAuthoritiesConverter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlUaaAuthenticationAuthoritiesConverter.java new file mode 100644 index 00000000000..876165e825e --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlUaaAuthenticationAuthoritiesConverter.java @@ -0,0 +1,97 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMember; +import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMembershipManager; +import org.opensaml.core.xml.XMLObject; +import org.opensaml.saml.saml2.core.Response; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import static java.util.Optional.of; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.GROUP_ATTRIBUTE_NAME; +import static org.cloudfoundry.identity.uaa.provider.saml.OpenSamlXmlUtils.getStringValue; +import static org.cloudfoundry.identity.uaa.util.UaaStringUtils.retainAllMatches; + +/** + * Part of the AuthenticationConverter used during SAML login flow. + * This handles the conversion of SAML Authorities to UAA Authorities. + */ +@Slf4j +@Getter +public class SamlUaaAuthenticationAuthoritiesConverter { + + private final ScimGroupExternalMembershipManager externalMembershipManager; + + public SamlUaaAuthenticationAuthoritiesConverter( + ScimGroupExternalMembershipManager externalMembershipManager) { + this.externalMembershipManager = externalMembershipManager; + } + + protected Set filterSamlAuthorities(SamlIdentityProviderDefinition definition, Collection samlAuthorities) { + List whiteList = of(definition.getExternalGroupsWhitelist()).orElse(Collections.EMPTY_LIST); + Set authorities = samlAuthorities.stream().map(s -> s.getAuthority()).collect(Collectors.toSet()); + Set result = retainAllMatches(authorities, whiteList); + log.debug("White listed external SAML groups:'{}'", result); + return result; + } + + protected Collection mapAuthorities(String origin, Collection authorities, String identityZoneId) { + Collection result = new LinkedList<>(); + log.debug("Mapping SAML authorities:" + authorities); + for (GrantedAuthority authority : authorities) { + String externalGroup = authority.getAuthority(); + log.debug("Attempting to map external group: {}", externalGroup); + for (ScimGroupExternalMember internalGroup : externalMembershipManager.getExternalGroupMapsByExternalGroup(externalGroup, origin, identityZoneId)) { + String internalName = internalGroup.getDisplayName(); + log.debug("Mapped external: '{}' to internal: '{}'", externalGroup, internalName); + result.add(new SimpleGrantedAuthority(internalName)); + } + } + return result; + } + + protected List retrieveSamlAuthorities(SamlIdentityProviderDefinition definition, Response response) { + if (definition.getAttributeMappings().get(GROUP_ATTRIBUTE_NAME) != null) { + List groupAttributeNames = getGroupAttributeNames(definition); + + List authorities = new ArrayList<>(); + response.getAssertions().stream().flatMap(assertion -> assertion.getAttributeStatements().stream()) + .flatMap(attributeStatement -> attributeStatement.getAttributes().stream()) + .filter(attribute -> groupAttributeNames.contains(attribute.getName()) || groupAttributeNames.contains(attribute.getFriendlyName())) + .filter(attribute -> attribute.getAttributeValues() != null) + .filter(attribute -> !attribute.getAttributeValues().isEmpty()) + .forEach(attribute -> { + for (XMLObject group : attribute.getAttributeValues()) { + authorities.add(new SamlUserAuthority(getStringValue(attribute.getName(), + definition, + group))); + } + }); + + return authorities; + } + return new ArrayList<>(); + } + + private List getGroupAttributeNames(SamlIdentityProviderDefinition definition) { + List attributeNames = new LinkedList<>(); + + if (definition.getAttributeMappings().get(GROUP_ATTRIBUTE_NAME) instanceof String value) { + attributeNames.add(value); + } else if (definition.getAttributeMappings().get(GROUP_ATTRIBUTE_NAME) instanceof Collection value) { + attributeNames.addAll(value); + } + return attributeNames; + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlUaaResponseAuthenticationConverter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlUaaResponseAuthenticationConverter.java index 087f171bdee..e5a49278954 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlUaaResponseAuthenticationConverter.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlUaaResponseAuthenticationConverter.java @@ -1,35 +1,21 @@ package org.cloudfoundry.identity.uaa.provider.saml; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; -import org.cloudfoundry.identity.uaa.authentication.manager.ExternalGroupAuthorizationEvent; -import org.cloudfoundry.identity.uaa.authentication.manager.InvitedUserAuthenticatedEvent; -import org.cloudfoundry.identity.uaa.authentication.manager.NewUserAuthenticatedEvent; +import org.cloudfoundry.identity.uaa.authentication.event.IdentityProviderAuthenticationSuccessEvent; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.provider.JdbcIdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.user.UaaUser; -import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; -import org.cloudfoundry.identity.uaa.user.UaaUserPrototype; +import org.cloudfoundry.identity.uaa.util.UaaUrlUtils; +import org.cloudfoundry.identity.uaa.web.UaaSavedRequestAwareAuthenticationSuccessHandler; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManager; -import org.opensaml.core.xml.XMLObject; -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.XSQName; -import org.opensaml.core.xml.schema.XSString; -import org.opensaml.core.xml.schema.XSURI; import org.opensaml.saml.saml2.core.Assertion; -import org.opensaml.saml.saml2.core.Attribute; -import org.opensaml.saml.saml2.core.AttributeStatement; import org.opensaml.saml.saml2.core.Response; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEventPublisher; @@ -39,108 +25,71 @@ import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.ProviderNotFoundException; import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest; -import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider; -import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; -import javax.xml.namespace.QName; -import java.time.Instant; import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.Set; import static org.cloudfoundry.identity.uaa.constants.OriginKeys.NotANumber; -import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.EMAIL_ATTRIBUTE_NAME; -import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.EMAIL_VERIFIED_ATTRIBUTE_NAME; -import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.FAMILY_NAME_ATTRIBUTE_NAME; -import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.GIVEN_NAME_ATTRIBUTE_NAME; -import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.PHONE_NUMBER_ATTRIBUTE_NAME; -import static org.cloudfoundry.identity.uaa.util.UaaHttpRequestUtils.isAcceptedInvitationAuthentication; /** - * + * AuthenticationConverter used during SAML login flow to convert a SAML response token to a UaaAuthentication. */ @Slf4j +@Getter public class SamlUaaResponseAuthenticationConverter - implements Converter, + implements Converter, ApplicationEventPublisherAware { public static final String AUTHENTICATION_CONTEXT_CLASS_REFERENCE = "acr"; private final IdentityZoneManager identityZoneManager; - //private static final AuthnRequestUnmarshaller authnRequestUnmarshaller; - private final UaaUserDatabase userDatabase; private final IdentityProviderProvisioning identityProviderProvisioning; - //private static final ParserPool parserPool; - - //private static final ResponseUnmarshaller responseUnmarshaller; - - // private final ScimGroupExternalMembershipManager externalMembershipManager; private ApplicationEventPublisher eventPublisher; + private final SamlUaaUserManager userManager; + private final SamlUaaAuthenticationAttributesConverter attributesConverter; + private final SamlUaaAuthenticationAuthoritiesConverter authoritiesConverter; + public SamlUaaResponseAuthenticationConverter(IdentityZoneManager identityZoneManager, - final UaaUserDatabase userDatabase, - final JdbcIdentityProviderProvisioning identityProviderProvisioning) { + final JdbcIdentityProviderProvisioning identityProviderProvisioning, + SamlUaaUserManager userManager, + SamlUaaAuthenticationAttributesConverter attributesConverter, + SamlUaaAuthenticationAuthoritiesConverter authoritiesConverter) { this.identityZoneManager = identityZoneManager; - this.userDatabase = userDatabase; this.identityProviderProvisioning = identityProviderProvisioning; + this.userManager = userManager; + this.attributesConverter = attributesConverter; + this.authoritiesConverter = authoritiesConverter; } @Override - public UaaAuthentication convert(OpenSaml4AuthenticationProvider.ResponseToken responseToken) { - // Do the default conversion - Saml2Authentication authentication = OpenSaml4AuthenticationProvider - .createDefaultResponseAuthenticationConverter() - .convert(responseToken); - - Saml2AuthenticationToken authenticationToken = responseToken.getToken(); - Response response = responseToken.getResponse(); + public UaaAuthentication convert(SamlLoginAuthenticationProvider.ResponseToken responseToken) { + Saml2AuthenticationToken authenticationToken = responseToken.token(); + Response response = responseToken.response(); + List assertions = response.getAssertions(); IdentityZone zone = identityZoneManager.getCurrentIdentityZone(); - log.debug(String.format("Initiating SAML authentication in zone '%s' domain '%s'", - zone.getId(), zone.getSubdomain())); - RelyingPartyRegistration relyingPartyRegistration = authenticationToken.getRelyingPartyRegistration(); - AbstractSaml2AuthenticationRequest authenticationRequest = authenticationToken.getAuthenticationRequest(); - - Assertion assertion = responseToken.getResponse().getAssertions().get(0); - String username = assertion.getSubject().getNameID().getValue(); - //UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); - - List samlAuthorities = List.copyOf(authenticationToken.getAuthorities()); - - LinkedMultiValueMap customAttributes = new LinkedMultiValueMap<>(); -// for (Map.Entry> entry : userAttributes.entrySet()) { -// if (entry.getKey().startsWith(USER_ATTRIBUTE_PREFIX)) { -// customAttributes.put(entry.getKey().substring(USER_ATTRIBUTE_PREFIX.length()), entry.getValue()); -// } -// } - - Set externalGroups = Set.of(); - boolean authenticated = true; - long authenticatedTime = System.currentTimeMillis(); - long expiresAt = -1; + log.debug("Initiating SAML authentication in zone '{}' domain '{}'", + zone.getId(), zone.getSubdomain()); - UaaPrincipal initialPrincipal = new UaaPrincipal(NotANumber, "marissa@test.org", authenticationToken.getName(), + RelyingPartyRegistration relyingPartyRegistration = authenticationToken.getRelyingPartyRegistration(); + String subjectName = assertions.get(0).getSubject().getNameID().getValue(); + UaaPrincipal initialPrincipal = new UaaPrincipal(NotANumber, subjectName, authenticationToken.getName(), relyingPartyRegistration.getRegistrationId(), authenticationToken.getName(), zone.getId()); - UaaAuthentication initialUaaAuthentication = new UaaAuthentication(initialPrincipal, - authenticationToken.getCredentials(), samlAuthorities, externalGroups, customAttributes, null, - authenticated, authenticatedTime, - expiresAt); - + log.debug("Mapped SAML authentication to IDP with origin '{}' and username '{}'", + relyingPartyRegistration.getRegistrationId(), initialPrincipal.getName()); String alias = relyingPartyRegistration.getRegistrationId(); -// String relayState = context.getRelayState(); boolean addNew; IdentityProvider idp; SamlIdentityProviderDefinition samlConfig; @@ -154,267 +103,89 @@ public UaaAuthentication convert(OpenSaml4AuthenticationProvider.ResponseToken r } catch (EmptyResultDataAccessException x) { throw new ProviderNotFoundException("No SAML identity provider found in zone for alias:" + alias); } -// - log.debug( - String.format( - "Mapped SAML authentication to IDP with origin '%s' and username '%s'", - idp.getOriginKey(), - initialPrincipal.getName() - ) - ); - - - //Collection samlAuthorities = retrieveSamlAuthorities(samlConfig, (SAMLCredential) result.getCredentials()); -// -// Collection authorities = - // Collection samlAuthoritinull; -// SamlIdentityProviderDefinition.ExternalGroupMappingMode groupMappingMode = idp.getConfig().getGroupMappingMode(); -// switch (groupMappingMode) { -// case EXPLICITLY_MAPPED: -// authorities = mapAuthorities(idp.getOriginKey(), samlAuthorities); -// break; -// case AS_SCOPES: -// authorities = new LinkedList<>(samlAuthorities); -// break; -// } -// -// Set filteredExternalGroups = filterSamlAuthorities(samlConfig, samlAuthorities); - initialUaaAuthentication.setAuthenticationMethods(Set.of("ext")); - MultiValueMap userAttributes = retrieveUserAttributes(samlConfig, response); - List acrValues = userAttributes.get(AUTHENTICATION_CONTEXT_CLASS_REFERENCE); - if (acrValues != null) { - initialUaaAuthentication.setAuthContextClassRef(Set.copyOf(acrValues)); - } -// -// if (samlConfig.getAuthnContext() != null) { -// if (Collections.disjoint(userAttributes.get(AUTHENTICATION_CONTEXT_CLASS_REFERENCE), samlConfig.getAuthnContext())) { -// throw new BadCredentialsException("Identity Provider did not authenticate with the requested AuthnContext."); -// } -// } -// - UaaUser user = createIfMissing(initialPrincipal, addNew, samlAuthorities, userAttributes); - UaaPrincipal newPrincipal = new UaaPrincipal(user); - UaaAuthentication newAuthentication = new UaaAuthentication(initialUaaAuthentication, newPrincipal); - - // publish(new IdentityProviderAuthenticationSuccessEvent(user, newAuthentication, OriginKeys.SAML, identityZoneManager.getCurrentIdentityZoneId())); -// if (samlConfig.isStoreCustomAttributes()) { -// userDatabase.storeUserInfo(user.getId(), -// new UserInfo() -// .setUserAttributes(resultUaaAuthentication.getUserAttributes()) -// .setRoles(new LinkedList(resultUaaAuthentication.getExternalGroups())) -// ); -// } -// configureRelayRedirect(relayState); -// - return newAuthentication; - } + MultiValueMap userAttributes = attributesConverter.retrieveUserAttributes(samlConfig, response); + List samlAuthorities = authoritiesConverter.retrieveSamlAuthorities(samlConfig, response); - /** - * Default conversion: - * Response response = responseToken.response; - * Saml2AuthenticationToken token = responseToken.token; - * Assertion assertion = CollectionUtils.firstElement(response.getAssertions()); - * String username = assertion.getSubject().getNameID().getValue(); - * Map> attributes = getAssertionAttributes(assertion); - * List sessionIndexes = getSessionIndexes(assertion); - * DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal(username, attributes, - * sessionIndexes); - * String registrationId = responseToken.token.getRelyingPartyRegistration().getRegistrationId(); - * principal.setRelyingPartyRegistrationId(registrationId); - * return new Saml2Authentication(principal, token.getSaml2Response(), - * AuthorityUtils.createAuthorityList("ROLE_USER")); - */ - - /* - * TODO: Move User Attributes Stuff - */ - public MultiValueMap retrieveUserAttributes(SamlIdentityProviderDefinition definition, Response response) { - log.debug(String.format("Retrieving SAML user attributes [zone:%s, origin:%s]", definition.getZoneId(), definition.getIdpEntityAlias())); - MultiValueMap userAttributes = new LinkedMultiValueMap<>(); - List assertions = response.getAssertions(); - if (assertions.isEmpty()) { - return userAttributes; - } - for (Assertion assertion : assertions) { - if (assertion.getAttributeStatements() != null) { - for (AttributeStatement statement : assertion.getAttributeStatements()) { - for (Attribute attribute : statement.getAttributes()) { - if (attribute.getAttributeValues() != null) { - for (XMLObject xmlObject : attribute.getAttributeValues()) { - String key = attribute.getName(); - String value = getStringValue(key, definition, xmlObject); - if (value != null) { - userAttributes.add(key, value); - } - } - } - } - } - } - } + log.debug("Mapped SAML authentication to IDP with origin '{}' and username '{}'", + idp.getOriginKey(), initialPrincipal.getName()); - if (definition != null && definition.getAttributeMappings() != null) { - for (Map.Entry attributeMapping : definition.getAttributeMappings().entrySet()) { - Object attributeKey = attributeMapping.getValue(); - if (attributeKey instanceof String) { - if (userAttributes.get(attributeKey) != null) { - String key = attributeMapping.getKey(); - userAttributes.addAll(key, userAttributes.get(attributeKey)); - } - } - } - } -// if (credential.getAuthenticationAssertion() != null && credential.getAuthenticationAssertion().getAuthnStatements() != null) { -// for (AuthnStatement statement : credential.getAuthenticationAssertion().getAuthnStatements()) { -// if (statement.getAuthnContext() != null && statement.getAuthnContext().getAuthnContextClassRef() != null) { -// userAttributes.add(AUTHENTICATION_CONTEXT_CLASS_REFERENCE, statement.getAuthnContext().getAuthnContextClassRef().getAuthnContextClassRef()); -// } -// } -// } - return userAttributes; - } + UaaUser user = userManager.createIfMissing(initialPrincipal, addNew, getMappedAuthorities( + idp, samlAuthorities), userAttributes); + + UaaAuthentication authentication = new UaaAuthentication( + new UaaPrincipal(user), + authenticationToken.getCredentials(), + user.getAuthorities(), + authoritiesConverter.filterSamlAuthorities(samlConfig, samlAuthorities), + attributesConverter.retrieveCustomUserAttributes(userAttributes), + null, + true, System.currentTimeMillis(), + -1); - protected String getStringValue(String key, SamlIdentityProviderDefinition definition, XMLObject xmlObject) { - String value = null; - if (xmlObject instanceof XSString) { - value = ((XSString) xmlObject).getValue(); - } else if (xmlObject instanceof XSAny) { - value = ((XSAny) xmlObject).getTextContent(); - } else if (xmlObject instanceof XSInteger) { - Integer i = ((XSInteger) xmlObject).getValue(); - value = i != null ? i.toString() : null; - } else if (xmlObject instanceof XSBoolean) { - XSBooleanValue b = ((XSBoolean) xmlObject).getValue(); - value = b != null && b.getValue() != null ? b.getValue().toString() : null; - } else if (xmlObject instanceof XSDateTime) { - Instant d = ((XSDateTime) xmlObject).getValue(); - value = d != null ? d.toString() : null; - } else if (xmlObject instanceof XSQName) { - QName name = ((XSQName) xmlObject).getValue(); - value = name != null ? name.toString() : null; - } else if (xmlObject instanceof XSURI) { - value = ((XSURI) xmlObject).getURI(); - } else if (xmlObject instanceof XSBase64Binary) { - value = ((XSBase64Binary) xmlObject).getValue(); + authentication.setAuthenticationMethods(Set.of("ext")); + setAuthContextClassRef(userAttributes, authentication, samlConfig); + + publish(new IdentityProviderAuthenticationSuccessEvent(user, authentication, OriginKeys.SAML, identityZoneManager.getCurrentIdentityZoneId())); + + if (samlConfig.isStoreCustomAttributes()) { + userManager.storeCustomAttributesAndRoles(user, authentication); } - if (value != null) { - log.debug(String.format("Found SAML user attribute %s of value %s [zone:%s, origin:%s]", key, value, definition.getZoneId(), definition.getIdpEntityAlias())); - return value; - } else if (xmlObject != null) { - log.debug(String.format("SAML user attribute %s at is not of type XSString or other recognizable type, %s [zone:%s, origin:%s]", key, xmlObject.getClass().getName(), definition.getZoneId(), definition.getIdpEntityAlias())); + AbstractSaml2AuthenticationRequest authenticationRequest = authenticationToken.getAuthenticationRequest(); + if (authenticationRequest != null) { + String relayState = authenticationRequest.getRelayState(); + configureRelayRedirect(relayState); } - return null; + + return authentication; } - /* - * TODO: Move User Creation Stuff - */ - - protected UaaUser createIfMissing(UaaPrincipal samlPrincipal, boolean addNew, Collection authorities, MultiValueMap userAttributes) { - UaaUser user = null; - String invitedUserId = null; - boolean is_invitation_acceptance = isAcceptedInvitationAuthentication(); - if (is_invitation_acceptance) { - invitedUserId = (String) RequestContextHolder.currentRequestAttributes().getAttribute("user_id", RequestAttributes.SCOPE_SESSION); - user = userDatabase.retrieveUserById(invitedUserId); - if (userAttributes.getFirst(EMAIL_ATTRIBUTE_NAME) != null) { - if (!userAttributes.getFirst(EMAIL_ATTRIBUTE_NAME).equalsIgnoreCase(user.getEmail())) { - throw new BadCredentialsException("SAML User email mismatch. Authenticated email doesn't match invited email."); - } - } else { - userAttributes = new LinkedMultiValueMap<>(userAttributes); - userAttributes.add(EMAIL_ATTRIBUTE_NAME, user.getEmail()); - } - addNew = false; - if (user.getUsername().equals(user.getEmail()) && !user.getUsername().equals(samlPrincipal.getName())) { - user = user.modifyUsername(samlPrincipal.getName()); - } - publish(new InvitedUserAuthenticatedEvent(user)); - user = userDatabase.retrieveUserById(invitedUserId); - } + private static void setAuthContextClassRef(MultiValueMap userAttributes, + UaaAuthentication authentication, SamlIdentityProviderDefinition samlConfig) { + + List acrValues = userAttributes.get(AUTHENTICATION_CONTEXT_CLASS_REFERENCE); + if (acrValues != null) { + authentication.setAuthContextClassRef(Set.copyOf(acrValues)); - boolean userModified = false; - UaaUser userWithSamlAttributes = getUser(samlPrincipal, userAttributes); - try { - if (user == null) { - user = userDatabase.retrieveUserByName(samlPrincipal.getName(), samlPrincipal.getOrigin()); - } - } catch (UsernameNotFoundException e) { - UaaUserPrototype uaaUser = userDatabase.retrieveUserPrototypeByEmail(userWithSamlAttributes.getEmail(), samlPrincipal.getOrigin()); - if (uaaUser != null) { - userModified = true; - user = new UaaUser(uaaUser.withUsername(samlPrincipal.getName())); - } else { - if (!addNew) { - throw new SamlLoginException("SAML user does not exist. " - + "You can correct this by creating a shadow user for the SAML user.", e); - } - publish(new NewUserAuthenticatedEvent(userWithSamlAttributes)); - try { - user = new UaaUser(userDatabase.retrieveUserPrototypeByName(samlPrincipal.getName(), samlPrincipal.getOrigin())); - } catch (UsernameNotFoundException ex) { - throw new BadCredentialsException("Unable to establish shadow user for SAML user:" + samlPrincipal.getName()); - } - } } - if (haveUserAttributesChanged(user, userWithSamlAttributes)) { - userModified = true; - user = user.modifyAttributes(userWithSamlAttributes.getEmail(), - userWithSamlAttributes.getGivenName(), - userWithSamlAttributes.getFamilyName(), - userWithSamlAttributes.getPhoneNumber(), - userWithSamlAttributes.getExternalId(), - user.isVerified() || userWithSamlAttributes.isVerified()); + if (samlConfig.getAuthnContext() != null) { + if (Collections.disjoint(acrValues, samlConfig.getAuthnContext())) { + throw new BadCredentialsException( + "Identity Provider did not authenticate with the requested AuthnContext."); + } } - publish( - new ExternalGroupAuthorizationEvent( - user, - userModified, - authorities, - true - ) - ); - user = userDatabase.retrieveUserById(user.getId()); - return user; } - protected UaaUser getUser(UaaPrincipal principal, MultiValueMap userAttributes) { - if (principal.getName() == null && userAttributes.getFirst(EMAIL_ATTRIBUTE_NAME) == null) { - throw new BadCredentialsException("Cannot determine username from credentials supplied"); + private Collection getMappedAuthorities( + IdentityProvider idp, + List samlAuthorities) { + Collection authorities = null; + SamlIdentityProviderDefinition.ExternalGroupMappingMode groupMappingMode = idp.getConfig().getGroupMappingMode(); + switch (groupMappingMode) { + case EXPLICITLY_MAPPED: + authorities = authoritiesConverter.mapAuthorities(idp.getOriginKey(), + samlAuthorities, identityZoneManager.getCurrentIdentityZoneId()); + break; + case AS_SCOPES: + authorities = List.copyOf(samlAuthorities); + break; } - - String name = principal.getName(); - return UaaUser.createWithDefaults(u -> - u.withId(OriginKeys.NotANumber) - .withUsername(name) - .withEmail(userAttributes.getFirst(EMAIL_ATTRIBUTE_NAME)) - .withPhoneNumber(userAttributes.getFirst(PHONE_NUMBER_ATTRIBUTE_NAME)) - .withPassword("") - .withGivenName(userAttributes.getFirst(GIVEN_NAME_ATTRIBUTE_NAME)) - .withFamilyName(userAttributes.getFirst(FAMILY_NAME_ATTRIBUTE_NAME)) - .withAuthorities(Collections.emptyList()) - .withVerified(Boolean.valueOf(userAttributes.getFirst(EMAIL_VERIFIED_ATTRIBUTE_NAME))) - .withOrigin(principal.getOrigin() != null ? principal.getOrigin() : OriginKeys.LOGIN_SERVER) - .withExternalId(name) - .withZoneId(principal.getZoneId()) - ); + return authorities; } - protected boolean haveUserAttributesChanged(UaaUser existingUser, UaaUser user) { - return existingUser.isVerified() != user.isVerified() || - !StringUtils.equals(existingUser.getGivenName(), user.getGivenName()) || - !StringUtils.equals(existingUser.getFamilyName(), user.getFamilyName()) || - !StringUtils.equals(existingUser.getPhoneNumber(), user.getPhoneNumber()) || - !StringUtils.equals(existingUser.getEmail(), user.getEmail()) || - !StringUtils.equals(existingUser.getExternalId(), user.getExternalId()); + public void configureRelayRedirect(String relayState) { + //configure relay state + if (UaaUrlUtils.isUrl(relayState)) { + RequestContextHolder.currentRequestAttributes() + .setAttribute( + UaaSavedRequestAwareAuthenticationSuccessHandler.URI_OVERRIDE_ATTRIBUTE, + relayState, + RequestAttributes.SCOPE_REQUEST + ); + } } - /* **************************************************** - ApplicationEventPublisherAware - **************************************************** */ - @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.eventPublisher = applicationEventPublisher; @@ -425,4 +196,13 @@ protected void publish(ApplicationEvent event) { eventPublisher.publishEvent(event); } } + +// @Override +// public void setUserDetails(SAMLUserDetailsService userDetails) { +// super.setUserDetails(userDetails); +// } + +// protected ExpiringUsernameAuthenticationToken getExpiringUsernameAuthenticationToken(Authentication authentication) { +// return (ExpiringUsernameAuthenticationToken) super.authenticate(authentication); +// } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlUaaUserManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlUaaUserManager.java new file mode 100644 index 00000000000..2be1a9c401b --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlUaaUserManager.java @@ -0,0 +1,194 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; +import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; +import org.cloudfoundry.identity.uaa.authentication.manager.ExternalGroupAuthorizationEvent; +import org.cloudfoundry.identity.uaa.authentication.manager.InvitedUserAuthenticatedEvent; +import org.cloudfoundry.identity.uaa.authentication.manager.NewUserAuthenticatedEvent; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.user.UaaUser; +import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; +import org.cloudfoundry.identity.uaa.user.UaaUserPrototype; +import org.cloudfoundry.identity.uaa.user.UserInfo; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationEventPublisherAware; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; + +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; + +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.EMAIL_ATTRIBUTE_NAME; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.EMAIL_VERIFIED_ATTRIBUTE_NAME; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.FAMILY_NAME_ATTRIBUTE_NAME; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.GIVEN_NAME_ATTRIBUTE_NAME; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.PHONE_NUMBER_ATTRIBUTE_NAME; +import static org.cloudfoundry.identity.uaa.util.UaaHttpRequestUtils.isAcceptedInvitationAuthentication; + +/** + * Part of the AuthenticationConverter used during SAML login flow. + * This handles User creation and storage in the database. + */ +public class SamlUaaUserManager implements ApplicationEventPublisherAware { + + ApplicationEventPublisher eventPublisher; + + public SamlUaaUserManager(UaaUserDatabase userDatabase) { + this.userDatabase = userDatabase; + } + + private final UaaUserDatabase userDatabase; + + protected UaaUser createIfMissing(UaaPrincipal samlPrincipal, + boolean addNew, + Collection authorities, + MultiValueMap userAttributes) { + + CreateIfMissingContext context = new CreateIfMissingContext(addNew, false, new LinkedMultiValueMap<>(userAttributes)); + UaaUser user = getAcceptedInvitationUser(samlPrincipal, context); + UaaUser userWithSamlAttributes = getUser(samlPrincipal, context.getUserAttributes()); + + try { + if (user == null) { + user = userDatabase.retrieveUserByName(samlPrincipal.getName(), samlPrincipal.getOrigin()); + } + } catch (UsernameNotFoundException e) { + UaaUserPrototype uaaUser = userDatabase.retrieveUserPrototypeByEmail(userWithSamlAttributes.getEmail(), samlPrincipal.getOrigin()); + if (uaaUser != null) { + context.setUserModified(true); + user = new UaaUser(uaaUser.withUsername(samlPrincipal.getName())); + } else { + if (!context.isAddNew()) { + throw new SamlLoginException("SAML user does not exist. " + + "You can correct this by creating a shadow user for the SAML user.", e); + } + publish(new NewUserAuthenticatedEvent(userWithSamlAttributes)); + try { + user = new UaaUser(userDatabase.retrieveUserPrototypeByName(samlPrincipal.getName(), samlPrincipal.getOrigin())); + } catch (UsernameNotFoundException ex) { + throw new BadCredentialsException("Unable to establish shadow user for SAML user:" + samlPrincipal.getName(), ex); + } + } + } + + if (haveUserAttributesChanged(user, userWithSamlAttributes)) { + context.setUserModified(true); + user = user.modifyAttributes(userWithSamlAttributes.getEmail(), + userWithSamlAttributes.getGivenName(), + userWithSamlAttributes.getFamilyName(), + userWithSamlAttributes.getPhoneNumber(), + userWithSamlAttributes.getExternalId(), + user.isVerified() || userWithSamlAttributes.isVerified()); + } + + publish(new ExternalGroupAuthorizationEvent(user, context.isUserModified(), authorities, true)); + + user = userDatabase.retrieveUserById(user.getId()); + return user; + } + + private UaaUser getAcceptedInvitationUser(UaaPrincipal samlPrincipal, CreateIfMissingContext context) { + if (!isAcceptedInvitationAuthentication()) { + return null; + } + + context.setAddNew(false); + String invitedUserId = (String) RequestContextHolder.currentRequestAttributes().getAttribute("user_id", RequestAttributes.SCOPE_SESSION); + UaaUser user = userDatabase.retrieveUserById(invitedUserId); + if (context.hasEmailAttribute()) { + if (!context.getEmailAttribute().equalsIgnoreCase(user.getEmail())) { + throw new BadCredentialsException("SAML User email mismatch. Authenticated email doesn't match invited email."); + } + } else { + context.addEmailAttribute(user.getEmail()); + } + + if (user.getUsername().equals(user.getEmail()) && !user.getUsername().equals(samlPrincipal.getName())) { + user = user.modifyUsername(samlPrincipal.getName()); + } + + publish(new InvitedUserAuthenticatedEvent(user)); + return userDatabase.retrieveUserById(invitedUserId); + } + + protected UaaUser getUser(UaaPrincipal principal, MultiValueMap userAttributes) { + if (principal.getName() == null && userAttributes.getFirst(EMAIL_ATTRIBUTE_NAME) == null) { + throw new BadCredentialsException("Cannot determine username from credentials supplied"); + } + + String name = principal.getName(); + return UaaUser.createWithDefaults(u -> + u.withId(OriginKeys.NotANumber) + .withUsername(name) + .withEmail(userAttributes.getFirst(EMAIL_ATTRIBUTE_NAME)) + .withPhoneNumber(userAttributes.getFirst(PHONE_NUMBER_ATTRIBUTE_NAME)) + .withPassword("") + .withGivenName(userAttributes.getFirst(GIVEN_NAME_ATTRIBUTE_NAME)) + .withFamilyName(userAttributes.getFirst(FAMILY_NAME_ATTRIBUTE_NAME)) + .withAuthorities(Collections.emptyList()) + .withVerified(Boolean.valueOf(userAttributes.getFirst(EMAIL_VERIFIED_ATTRIBUTE_NAME))) + .withOrigin(principal.getOrigin() != null ? principal.getOrigin() : OriginKeys.LOGIN_SERVER) + .withExternalId(name) + .withZoneId(principal.getZoneId()) + ); + } + + protected void storeCustomAttributesAndRoles(UaaUser user, UaaAuthentication authentication) { + userDatabase.storeUserInfo(user.getId(), + new UserInfo() + .setUserAttributes(authentication.getUserAttributes()) + .setRoles(new LinkedList(authentication.getExternalGroups())) + ); + } + + protected static boolean haveUserAttributesChanged(UaaUser existingUser, UaaUser user) { + return existingUser.isVerified() != user.isVerified() || + !StringUtils.equals(existingUser.getGivenName(), user.getGivenName()) || + !StringUtils.equals(existingUser.getFamilyName(), user.getFamilyName()) || + !StringUtils.equals(existingUser.getPhoneNumber(), user.getPhoneNumber()) || + !StringUtils.equals(existingUser.getEmail(), user.getEmail()) || + !StringUtils.equals(existingUser.getExternalId(), user.getExternalId()); + } + + @Override + public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { + this.eventPublisher = applicationEventPublisher; + } + + protected void publish(ApplicationEvent event) { + if (eventPublisher != null) { + eventPublisher.publishEvent(event); + } + } + + @Data + @AllArgsConstructor + public static class CreateIfMissingContext{ + boolean addNew; + boolean userModified; + MultiValueMap userAttributes; + + public String getEmailAttribute() { + return userAttributes.getFirst(EMAIL_ATTRIBUTE_NAME); + } + + public boolean hasEmailAttribute() { + return userAttributes.getFirst(EMAIL_ATTRIBUTE_NAME) != null; + } + + public void addEmailAttribute(String value) { + userAttributes.add(EMAIL_ATTRIBUTE_NAME, value); + } + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaAuthority.java b/server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaAuthority.java index e14bb4a5367..d9f9ead96a5 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaAuthority.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaAuthority.java @@ -13,6 +13,7 @@ package org.cloudfoundry.identity.uaa.user; import com.fasterxml.jackson.annotation.JsonCreator; +import lombok.Getter; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; @@ -30,7 +31,10 @@ */ public enum UaaAuthority implements GrantedAuthority { - UAA_INVITED("uaa.invited", 1), UAA_ADMIN("uaa.admin", 1), UAA_USER("uaa.user", 0), UAA_NONE("uaa.none", -1); + UAA_INVITED("uaa.invited", 1), + UAA_ADMIN("uaa.admin", 1), + UAA_USER("uaa.user", 0), + UAA_NONE("uaa.none", -1); public static final List ADMIN_AUTHORITIES = List.of(UAA_ADMIN, UAA_USER); @@ -40,6 +44,10 @@ public enum UaaAuthority implements GrantedAuthority { private final int value; + /** + * The name of the type of user, either "uaa.admin" or "uaa.user". + */ + @Getter private final String userType; UaaAuthority(String userType, int value) { @@ -51,15 +59,6 @@ public int value() { return value; } - /** - * The name of the type of user, either "uaa.admin" or "uaa.user". - * - * @return a user type name - */ - public String getUserType() { - return userType; - } - /** * The authority granted by this value (same as user type). * @@ -84,6 +83,6 @@ public static UaaAuthority fromAuthorities(String authorities) { public static GrantedAuthority authority(String value) { return value.equals("uaa.admin") ? UAA_ADMIN : value.contains("uaa.user") ? UAA_USER - : new SimpleGrantedAuthority(value); + : new SimpleGrantedAuthority(value); } } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2TestUtils.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2TestUtils.java index b01e7325f22..023e4f6abf2 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2TestUtils.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/Saml2TestUtils.java @@ -69,6 +69,7 @@ public final class Saml2TestUtils { private static final String ASSERTING_PARTY_ENTITY_ID = "https://some.idp.test/saml2/idp"; private Saml2TestUtils() { + throw new java.lang.UnsupportedOperationException("This is a utility class and cannot be instantiated"); } public static Saml2AuthenticationToken authenticationToken() { diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlLoginAuthenticationProviderTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlLoginAuthenticationProviderTests.java index c7849e9cb31..6b38b9aa364 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlLoginAuthenticationProviderTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlLoginAuthenticationProviderTests.java @@ -24,8 +24,8 @@ import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimUserProvisioning; import org.cloudfoundry.identity.uaa.test.TestUtils; import org.cloudfoundry.identity.uaa.user.JdbcUaaUserDatabase; +import org.cloudfoundry.identity.uaa.user.UaaAuthority; import org.cloudfoundry.identity.uaa.user.UaaUser; -import org.cloudfoundry.identity.uaa.user.UaaUserPrototype; import org.cloudfoundry.identity.uaa.user.UserInfo; import org.cloudfoundry.identity.uaa.util.TimeService; import org.cloudfoundry.identity.uaa.util.TimeServiceImpl; @@ -35,9 +35,11 @@ import org.cloudfoundry.identity.uaa.zone.JdbcIdentityZoneProvisioning; import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManager; import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManagerImpl; +import org.joda.time.DateTime; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EmptySource; @@ -45,10 +47,12 @@ import org.junit.jupiter.params.provider.ValueSource; import org.opensaml.core.config.InitializationException; import org.opensaml.core.config.InitializationService; +import org.opensaml.saml.saml2.core.AuthnContext; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEventPublisher; +import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.mock.web.MockHttpServletRequest; @@ -56,8 +60,10 @@ import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken; import org.springframework.util.LinkedMultiValueMap; import org.springframework.web.context.request.RequestAttributes; @@ -67,6 +73,7 @@ import javax.servlet.ServletContext; import java.sql.SQLException; +import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -86,13 +93,12 @@ import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.USER_ATTRIBUTE_PREFIX; import static org.cloudfoundry.identity.uaa.provider.saml.Saml2TestUtils.authenticationToken; import static org.cloudfoundry.identity.uaa.test.ModelTestUtils.getResourceAsString; -import static org.cloudfoundry.identity.uaa.util.AssertThrowsWithMessage.assertThrowsWithMessageThat; -import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.Mockito.mock; @@ -116,7 +122,8 @@ class SamlLoginAuthenticationProviderTests { private static final String MANAGER = "manager"; private static final String JOHN_THE_SLOTH = "John the Sloth"; private static final String KARI_THE_ANT_EATER = "Kari the Ant Eater"; - private static final String IDP_META_DATA = getResourceAsString(SamlLoginAuthenticationProviderTests.class, "IDP_META_DATA.xml"); + private static final String IDP_META_DATA = getResourceAsString( + SamlLoginAuthenticationProviderTests.class, "IDP_META_DATA.xml"); private static final String TEST_EMAIL = "john.doe@example.com"; private static final String TEST_USERNAME = "test@saml.user"; @@ -141,9 +148,10 @@ class SamlLoginAuthenticationProviderTests { @Autowired private PasswordEncoder passwordEncoder; - private static ScimUser createSamlUser(String username, String zoneId, ScimUserProvisioning userProvisioning) { + private static ScimUser createSamlUser(String username, String zoneId, + ScimUserProvisioning userProvisioning) { ScimUser user = new ScimUser("", username, "Marissa", "Bloggs"); - user.setPrimaryEmail("marissa.bloggs@test.com"); + user.setPrimaryEmail(TEST_EMAIL); user.setOrigin(OriginKeys.SAML); return userProvisioning.createUser(user, "", zoneId); } @@ -171,50 +179,59 @@ void configureProvider() throws SecurityException, SQLException, InitializationE InitializationService.initialize(); ScimGroupProvisioning groupProvisioning = new JdbcScimGroupProvisioning( - namedJdbcTemplate, new JdbcPagingListFactory(namedJdbcTemplate, limitSqlAdapter), dbUtils); - identityZoneManager.getCurrentIdentityZone().getConfig().getUserConfig().setDefaultGroups(Collections.singletonList(UAA_USER)); - identityZoneManager.getCurrentIdentityZone().getConfig().getUserConfig().setAllowedGroups(Arrays.asList(UAA_USER, SAML_USER, - SAML_ADMIN, SAML_TEST, SAML_NOT_MAPPED, UAA_SAML_USER, UAA_SAML_ADMIN, UAA_SAML_TEST)); - groupProvisioning.createOrGet(new ScimGroup(null, UAA_USER, identityZoneManager.getCurrentIdentityZone().getId()), identityZoneManager.getCurrentIdentityZone().getId()); - - userProvisioning = new JdbcScimUserProvisioning(namedJdbcTemplate, new JdbcPagingListFactory(namedJdbcTemplate, limitSqlAdapter), passwordEncoder, new IdentityZoneManagerImpl(), new JdbcIdentityZoneProvisioning(jdbcTemplate)); - - uaaSamlUser = groupProvisioning.create(new ScimGroup(null, UAA_SAML_USER, IdentityZone.getUaaZoneId()), identityZoneManager.getCurrentIdentityZone().getId()); - uaaSamlAdmin = groupProvisioning.create(new ScimGroup(null, UAA_SAML_ADMIN, IdentityZone.getUaaZoneId()), identityZoneManager.getCurrentIdentityZone().getId()); - ScimGroup uaaSamlTest = groupProvisioning.create(new ScimGroup(null, UAA_SAML_TEST, IdentityZone.getUaaZoneId()), identityZoneManager.getCurrentIdentityZone().getId()); + namedJdbcTemplate, new JdbcPagingListFactory(namedJdbcTemplate, limitSqlAdapter), + dbUtils); + identityZoneManager.getCurrentIdentityZone().getConfig().getUserConfig() + .setDefaultGroups(Collections.singletonList(UAA_USER)); + identityZoneManager.getCurrentIdentityZone().getConfig().getUserConfig() + .setAllowedGroups(Arrays.asList(UAA_USER, SAML_USER, + SAML_ADMIN, SAML_TEST, SAML_NOT_MAPPED, UAA_SAML_USER, UAA_SAML_ADMIN, + UAA_SAML_TEST)); + groupProvisioning.createOrGet( + new ScimGroup(null, UAA_USER, identityZoneManager.getCurrentIdentityZone().getId()), + identityZoneManager.getCurrentIdentityZone().getId()); + + userProvisioning = new JdbcScimUserProvisioning(namedJdbcTemplate, + new JdbcPagingListFactory(namedJdbcTemplate, limitSqlAdapter), passwordEncoder, + new IdentityZoneManagerImpl(), new JdbcIdentityZoneProvisioning(jdbcTemplate)); + + uaaSamlUser = groupProvisioning.create( + new ScimGroup(null, UAA_SAML_USER, IdentityZone.getUaaZoneId()), + identityZoneManager.getCurrentIdentityZone().getId()); + uaaSamlAdmin = groupProvisioning.create( + new ScimGroup(null, UAA_SAML_ADMIN, IdentityZone.getUaaZoneId()), + identityZoneManager.getCurrentIdentityZone().getId()); + ScimGroup uaaSamlTest = groupProvisioning.create( + new ScimGroup(null, UAA_SAML_TEST, IdentityZone.getUaaZoneId()), + identityZoneManager.getCurrentIdentityZone().getId()); JdbcScimGroupMembershipManager membershipManager = new JdbcScimGroupMembershipManager( jdbcTemplate, new TimeServiceImpl(), userProvisioning, null, dbUtils); membershipManager.setScimGroupProvisioning(groupProvisioning); - ScimUserBootstrap bootstrap = new ScimUserBootstrap(userProvisioning, groupProvisioning, membershipManager, Collections.emptyList(), false, Collections.emptyList()); + ScimUserBootstrap bootstrap = new ScimUserBootstrap(userProvisioning, groupProvisioning, + membershipManager, Collections.emptyList(), false, Collections.emptyList()); externalManager = new JdbcScimGroupExternalMembershipManager(jdbcTemplate, dbUtils); externalManager.setScimGroupProvisioning(groupProvisioning); - externalManager.mapExternalGroup(uaaSamlUser.getId(), SAML_USER, OriginKeys.SAML, identityZoneManager.getCurrentIdentityZone().getId()); - externalManager.mapExternalGroup(uaaSamlAdmin.getId(), SAML_ADMIN, OriginKeys.SAML, identityZoneManager.getCurrentIdentityZone().getId()); - externalManager.mapExternalGroup(uaaSamlTest.getId(), SAML_TEST, OriginKeys.SAML, identityZoneManager.getCurrentIdentityZone().getId()); - -// consumer = mock(WebSSOProfileConsumer.class); -// SAMLCredential credential = getUserCredential("marissa-saml", "Marissa", "Bloggs", "marissa.bloggs@test.com", TEST_PHONE_NUMBER); -// -// when(consumer.processAuthenticationResponse(any())).thenReturn(credential); + externalManager.mapExternalGroup(uaaSamlUser.getId(), SAML_USER, OriginKeys.SAML, + identityZoneManager.getCurrentIdentityZone().getId()); + externalManager.mapExternalGroup(uaaSamlAdmin.getId(), SAML_ADMIN, OriginKeys.SAML, + identityZoneManager.getCurrentIdentityZone().getId()); + externalManager.mapExternalGroup(uaaSamlTest.getId(), SAML_TEST, OriginKeys.SAML, + identityZoneManager.getCurrentIdentityZone().getId()); TimeService timeService = mock(TimeService.class); DatabaseUrlModifier databaseUrlModifier = mock(DatabaseUrlModifier.class); when(databaseUrlModifier.getDatabaseType()).thenReturn(Vendor.unknown); - userDatabase = new JdbcUaaUserDatabase(jdbcTemplate, timeService, false, identityZoneManager, + userDatabase = new JdbcUaaUserDatabase(jdbcTemplate, timeService, false, + identityZoneManager, databaseUrlModifier, new DbUtils()); providerProvisioning = new JdbcIdentityProviderProvisioning(jdbcTemplate); publisher = new CreateUserPublisher(bootstrap); SamlAuthenticationFilterConfig samlAuthenticationFilterConfig = new SamlAuthenticationFilterConfig(); authprovider = samlAuthenticationFilterConfig.samlAuthenticationProvider( - identityZoneManager, - userDatabase, - providerProvisioning); - if (authprovider instanceof SamlLoginAuthenticationProvider authProvider) { - authProvider.setApplicationEventPublisher(publisher); - } + identityZoneManager, userDatabase, providerProvisioning, externalManager, publisher); providerDefinition = new SamlIdentityProviderDefinition(); providerDefinition.setMetaDataLocation(String.format(IDP_META_DATA, OriginKeys.SAML)); @@ -227,7 +244,8 @@ void configureProvider() throws SecurityException, SQLException, InitializationE provider.setActive(true); provider.setType(OriginKeys.SAML); provider.setConfig(providerDefinition); - provider = providerProvisioning.create(provider, identityZoneManager.getCurrentIdentityZone().getId()); + provider = providerProvisioning.create(provider, + identityZoneManager.getCurrentIdentityZone().getId()); } @AfterEach @@ -246,14 +264,31 @@ void testAuthenticateSimple() { @NullSource @EmptySource void relayRedirectRejectsNonUrls(String url) { - assumeThat(authprovider).isInstanceOf(SamlLoginAuthenticationProvider.class); SamlLoginAuthenticationProvider authprovider = (SamlLoginAuthenticationProvider) this.authprovider; - authprovider.configureRelayRedirect(url); + authprovider.getResponseAuthenticationConverter().configureRelayRedirect(url); assertThat(RequestContextHolder.currentRequestAttributes() - .getAttribute(UaaSavedRequestAwareAuthenticationSuccessHandler.URI_OVERRIDE_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST)) + .getAttribute(UaaSavedRequestAwareAuthenticationSuccessHandler.URI_OVERRIDE_ATTRIBUTE, + RequestAttributes.SCOPE_REQUEST)) .isNull(); } + @Test + void relayRedirectIsSetForUrl() { + String redirectUrl = "https://www.cloudfoundry.org"; + + Saml2AuthenticationToken authenticationToken = authenticationToken(); + AbstractSaml2AuthenticationRequest mockAuthenticationRequest = authenticationToken.getAuthenticationRequest(); + when(mockAuthenticationRequest.getRelayState()).thenReturn(redirectUrl); + UaaAuthentication uaaAuthentication = authenticate(authenticationToken); + + assertThat(RequestContextHolder.currentRequestAttributes() + .getAttribute(UaaSavedRequestAwareAuthenticationSuccessHandler.URI_OVERRIDE_ATTRIBUTE, + RequestAttributes.SCOPE_REQUEST)) + .isEqualTo(redirectUrl); + assertThat(uaaAuthentication.getAuthContextClassRef()).contains( + AuthnContext.PASSWORD_AUTHN_CTX); + } + @Test void testAuthenticationEvents() { authprovider.authenticate(authenticationToken()); @@ -261,44 +296,34 @@ void testAuthenticationEvents() { assertInstanceOf(IdentityProviderAuthenticationSuccessEvent.class, publisher.events.get(2)); } - @Test - void relayRedirectIsSetForUrl() { - String redirectUrl = "https://www.cloudfoundry.org"; - Saml2AuthenticationToken mockAuthenticationToken = authenticationToken(); - when(mockAuthenticationToken.getAuthenticationRequest().getRelayState()).thenReturn(redirectUrl); - UaaAuthentication authentication = authenticate(mockAuthenticationToken); - //assertThat(uaaAuthentication.getAuthContextClassRef()).contains(AuthnContext.PASSWORD_AUTHN_CTX); - verify(mockAuthenticationToken.getAuthenticationRequest(), times(1)).getRelayState(); - //assertThat(RequestContextHolder.currentRequestAttributes().getAttribute(UaaSavedRequestAwareAuthenticationSuccessHandler.URI_OVERRIDE_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST)) - // .isEqualTo(redirectUrl); - } @Test void saml_authentication_contains_acr() { Saml2AuthenticationToken mockAuthenticationToken = authenticationToken(); - UaaAuthentication authentication = authenticate(mockAuthenticationToken); - //assertThat(uaaAuthentication.getAuthContextClassRef()).contains(AuthnContext.PASSWORD_AUTHN_CTX); + UaaAuthentication uaaAuthentication = authenticate(mockAuthenticationToken); + assertThat(uaaAuthentication.getAuthContextClassRef()).contains( + AuthnContext.PASSWORD_AUTHN_CTX); verify(mockAuthenticationToken.getAuthenticationRequest(), times(1)).getRelayState(); - assertThat(RequestContextHolder.currentRequestAttributes().getAttribute(UaaSavedRequestAwareAuthenticationSuccessHandler.URI_OVERRIDE_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST)) + assertThat(RequestContextHolder.currentRequestAttributes() + .getAttribute(UaaSavedRequestAwareAuthenticationSuccessHandler.URI_OVERRIDE_ATTRIBUTE, + RequestAttributes.SCOPE_REQUEST)) .isNull(); } @Test - @Disabled("SAML test doesn't compile") - void test_multiple_group_attributes() { - providerDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, Arrays.asList("2ndgroups", "groups")); + void multipleGroupAttributesMapping() { + providerDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, + Arrays.asList("2ndgroups", "groups")); provider.setConfig(providerDefinition); providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); -// UaaAuthentication authentication = getAuthentication(authprovider); -// assertEquals(4, authentication.getAuthorities().size(), "Four authorities should have been granted!"); -// assertThat(authentication.getAuthorities(), -// containsInAnyOrder( -// new SimpleGrantedAuthority(UAA_SAML_ADMIN), -// new SimpleGrantedAuthority(UAA_SAML_USER), -// new SimpleGrantedAuthority(UAA_SAML_TEST), -// new SimpleGrantedAuthority(UaaAuthority.UAA_USER.getAuthority()) -// ) -// ); + UaaAuthentication authentication = authenticate(); + assertThat(authentication.getAuthorities()). + containsExactlyInAnyOrder( + new SimpleGrantedAuthority(UAA_SAML_ADMIN), + new SimpleGrantedAuthority(UAA_SAML_USER), + new SimpleGrantedAuthority(UAA_SAML_TEST), + new SimpleGrantedAuthority(UaaAuthority.UAA_USER.getAuthority()) + ); } @Test @@ -308,43 +333,38 @@ void authenticationContainsAmr() { } @Test - @Disabled("SAML test doesn't compile") void test_external_groups_as_scopes() { - providerDefinition.setGroupMappingMode(SamlIdentityProviderDefinition.ExternalGroupMappingMode.AS_SCOPES); - providerDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, Arrays.asList("2ndgroups", "groups")); + providerDefinition.setGroupMappingMode( + SamlIdentityProviderDefinition.ExternalGroupMappingMode.AS_SCOPES); + providerDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, + Arrays.asList("2ndgroups", "groups")); provider.setConfig(providerDefinition); providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); -// UaaAuthentication authentication = getAuthentication(authprovider); -// assertThat(authentication.getAuthorities(), -// containsInAnyOrder( -// new SimpleGrantedAuthority(SAML_ADMIN), -// new SimpleGrantedAuthority(SAML_USER), -// new SimpleGrantedAuthority(SAML_TEST), -// new SimpleGrantedAuthority(SAML_NOT_MAPPED), -// new SimpleGrantedAuthority(UaaAuthority.UAA_USER.getAuthority()) -// ) -// ); + UaaAuthentication authentication = authenticate(); + assertThat(authentication.getAuthorities()).containsExactlyInAnyOrder( + new SimpleGrantedAuthority(SAML_ADMIN), + new SimpleGrantedAuthority(SAML_USER), + new SimpleGrantedAuthority(SAML_TEST), + new SimpleGrantedAuthority(SAML_NOT_MAPPED), + new SimpleGrantedAuthority(UaaAuthority.UAA_USER.getAuthority()) + ); } @Test - @Disabled("SAML test doesn't compile") void test_group_mapping() { providerDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, "groups"); provider.setConfig(providerDefinition); providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); -// UaaAuthentication authentication = getAuthentication(authprovider); -// assertEquals(3, authentication.getAuthorities().size(), "Three authorities should have been granted!"); -// assertThat(authentication.getAuthorities(), -// containsInAnyOrder( -// new SimpleGrantedAuthority(UAA_SAML_ADMIN), -// new SimpleGrantedAuthority(UAA_SAML_USER), -// new SimpleGrantedAuthority(UaaAuthority.UAA_USER.getAuthority()) -// ) -// ); + UaaAuthentication authentication = authenticate(); + assertThat(authentication.getAuthorities()).containsExactlyInAnyOrder( + new SimpleGrantedAuthority(UAA_SAML_ADMIN), + new SimpleGrantedAuthority(UAA_SAML_USER), + new SimpleGrantedAuthority(UaaAuthority.UAA_USER.getAuthority()) + ); + } @Test - @Disabled("SAML test doesn't compile") void test_non_string_attributes() { providerDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX + "XSURI", "XSURI"); providerDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX + "XSAny", "XSAny"); @@ -356,49 +376,44 @@ void test_non_string_attributes() { provider.setConfig(providerDefinition); providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); -// UaaAuthentication authentication = getAuthentication(authprovider); -// assertEquals("http://localhost:8080/someuri", authentication.getUserAttributes().getFirst("XSURI")); -// assertEquals("XSAnyValue", authentication.getUserAttributes().getFirst("XSAny")); -// assertEquals("XSQNameValue", authentication.getUserAttributes().getFirst("XSQName")); -// assertEquals("3", authentication.getUserAttributes().getFirst("XSInteger")); -// assertEquals("true", authentication.getUserAttributes().getFirst("XSBoolean")); -// assertEquals(new DateTime(0).toString(), authentication.getUserAttributes().getFirst("XSDateTime")); -// assertEquals("00001111", authentication.getUserAttributes().getFirst("XSBase64Binary")); + UaaAuthentication authentication = authenticate(); + + assertThat(authentication.getUserAttributes()) + .containsEntry("XSURI", List.of("http://localhost:8080/someuri")) + .containsEntry("XSAny", List.of("XSAnyValue")) + .containsEntry("XSQName", List.of("XSQNameValue")) + .containsEntry("XSInteger", List.of("3")) + .containsEntry("XSBoolean", List.of("true")) + .containsEntry("XSDateTime", List.of("1970-01-01T00:00:00Z")) + .containsEntry("XSBase64Binary", List.of("00001111")); } @Test - @Disabled("SAML test doesn't compile") - void externalGroup_NotMapped_ToScope() { - try { - externalManager.unmapExternalGroup(uaaSamlUser.getId(), SAML_USER, OriginKeys.SAML, identityZoneManager.getCurrentIdentityZone().getId()); - externalManager.unmapExternalGroup(uaaSamlAdmin.getId(), SAML_ADMIN, OriginKeys.SAML, identityZoneManager.getCurrentIdentityZone().getId()); - providerDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, "groups"); - provider.setConfig(providerDefinition); - providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); -// UaaAuthentication authentication = getAuthentication(authprovider); -// assertEquals(1, authentication.getAuthorities().size(), "Three authorities should have been granted!"); -// assertThat(authentication.getAuthorities(), -// not(containsInAnyOrder( -// new SimpleGrantedAuthority(UAA_SAML_ADMIN), -// new SimpleGrantedAuthority(UAA_SAML_USER) -// )) -// ); - } finally { - externalManager.mapExternalGroup(uaaSamlUser.getId(), SAML_USER, OriginKeys.SAML, identityZoneManager.getCurrentIdentityZone().getId()); - externalManager.mapExternalGroup(uaaSamlAdmin.getId(), SAML_ADMIN, OriginKeys.SAML, identityZoneManager.getCurrentIdentityZone().getId()); - } + void externalGroupNotMappedToScope() { + externalManager.unmapExternalGroup(uaaSamlUser.getId(), SAML_USER, OriginKeys.SAML, + identityZoneManager.getCurrentIdentityZone().getId()); + externalManager.unmapExternalGroup(uaaSamlAdmin.getId(), SAML_ADMIN, OriginKeys.SAML, + identityZoneManager.getCurrentIdentityZone().getId()); + providerDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, "groups"); + provider.setConfig(providerDefinition); + providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); + UaaAuthentication authentication = authenticate(); + assertThat(authentication.getAuthorities()).hasSize(1).doesNotContainAnyElementsOf(List.of( + new SimpleGrantedAuthority(UAA_SAML_ADMIN), + new SimpleGrantedAuthority(UAA_SAML_USER)) + ); } @Test - @Disabled("SAML test doesn't compile") - void test_group_attribute_not_set() { -// UaaAuthentication uaaAuthentication = getAuthentication(authprovider); -// assertEquals(1, uaaAuthentication.getAuthorities().size(), "Only uaa.user should have been granted"); -// assertEquals(UaaAuthority.UAA_USER.getAuthority(), uaaAuthentication.getAuthorities().iterator().next().getAuthority()); + void uaaUserAuthorityGrantedIfNoOtherProvided() { + UaaAuthentication uaaAuthentication = authenticate(); + assertThat(uaaAuthentication.getAuthorities()).containsExactly( + new SimpleGrantedAuthority(UaaAuthority.UAA_USER.getAuthority()) + ); } @Test - void dontAdd_external_groups_to_authentication_without_whitelist() { + void dontAddExternalGroupsToAuthenticationWithoutWhitelist() { providerDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, "groups"); provider.setConfig(providerDefinition); providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); @@ -408,26 +423,24 @@ void dontAdd_external_groups_to_authentication_without_whitelist() { } @Test - @Disabled("SAML test doesn't compile") - void add_external_groups_to_authentication_with_whitelist() { + void addExternalGroupsToAuthenticationWithWhitelist() { providerDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, "groups"); providerDefinition.addWhiteListedGroup(SAML_ADMIN); provider.setConfig(providerDefinition); providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); -// UaaAuthentication authentication = getAuthentication(authprovider); -// assertEquals(Collections.singleton(SAML_ADMIN), authentication.getExternalGroups()); + UaaAuthentication authentication = authenticate(); + assertEquals(Collections.singleton(SAML_ADMIN), authentication.getExternalGroups()); } @Test - @Disabled("SAML test doesn't compile") - void add_external_groups_to_authentication_with_wildcard_whitelist() { + void addExternalGroupsToAuthenticationWithWildcardWhitelist() { providerDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, "groups"); providerDefinition.addWhiteListedGroup("saml*"); provider.setConfig(providerDefinition); providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); -// UaaAuthentication authentication = getAuthentication(authprovider); -// assertThat(authentication.getExternalGroups(), containsInAnyOrder(SAML_USER, SAML_ADMIN, SAML_NOT_MAPPED)); + UaaAuthentication authentication = authenticate(); + assertThat(authentication.getExternalGroups()).containsExactlyInAnyOrder(SAML_USER, SAML_ADMIN, SAML_NOT_MAPPED); } @Test @@ -449,7 +462,8 @@ void update_invitedUser_whose_username_is_notEmail() throws Exception { @Test @Disabled("SAML test doesn't compile") - void invitedUser_authentication_whenAuthenticatedEmailDoesNotMatchInvitedEmail() throws Exception { + void invitedUser_authentication_whenAuthenticatedEmailDoesNotMatchInvitedEmail() + throws Exception { Map attributeMappings = new HashMap<>(); attributeMappings.put("email", "emailAddress"); providerDefinition.setAttributeMappings(attributeMappings); @@ -475,7 +489,8 @@ private ScimUser getInvitedUser() { invitedUser.setVerified(false); invitedUser.setPrimaryEmail("marissa.invited@test.org"); invitedUser.setOrigin(OriginKeys.UAA); - ScimUser scimUser = userProvisioning.createUser(invitedUser, "getInvitedUser-password", identityZoneManager.getCurrentIdentityZone().getId()); + ScimUser scimUser = userProvisioning.createUser(invitedUser, "getInvitedUser-password", + identityZoneManager.getCurrentIdentityZone().getId()); RequestAttributes attributes = new ServletRequestAttributes(new MockHttpServletRequest()); attributes.setAttribute("IS_INVITE_ACCEPTANCE", true, RequestAttributes.SCOPE_SESSION); @@ -486,8 +501,7 @@ private ScimUser getInvitedUser() { } @Test - @Disabled("SAML test doesn't compile") - void update_existingUser_if_attributes_different() throws Exception { + void updateExistingUserWithDifferentAttributes() throws Exception { try { userDatabase.retrieveUserByName(TEST_USERNAME, OriginKeys.SAML); fail("user should not exist"); @@ -496,37 +510,26 @@ void update_existingUser_if_attributes_different() throws Exception { authenticate(); UaaUser user = userDatabase.retrieveUserByName(TEST_USERNAME, OriginKeys.SAML); - assertFalse(user.isVerified()); + assertThat(user).returns("john.doe", UaaUser::getGivenName) + .returns(TEST_EMAIL, UaaUser::getEmail); + Map attributeMappings = new HashMap<>(); attributeMappings.put("given_name", "firstName"); attributeMappings.put("email", "emailAddress"); - attributeMappings.put("email_verified", "emailVerified"); providerDefinition.setAttributeMappings(attributeMappings); provider.setConfig(providerDefinition); providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); - -// SAMLCredential credential = getUserCredential("marissa-saml", "Marissa-changed", null, "marissa.bloggs@change.org", null); -// when(consumer.processAuthenticationResponse(any())).thenReturn(credential); authenticate(); user = userDatabase.retrieveUserByName(TEST_USERNAME, OriginKeys.SAML); - assertEquals("Marissa-changed", user.getGivenName()); - assertEquals("marissa.bloggs@change.org", user.getEmail()); - assertFalse(user.isVerified()); + assertThat(user).returns("John", UaaUser::getGivenName) + .returns(TEST_EMAIL, UaaUser::getEmail); -// credential = getUserCredential("marissa-saml", "Marissa-changed", null, "marissa.bloggs@change.org", null, true); -// when(consumer.processAuthenticationResponse(any())).thenReturn(credential); - authenticate(); - - user = userDatabase.retrieveUserByName(TEST_USERNAME, OriginKeys.SAML); - assertEquals("Marissa-changed", user.getGivenName()); - assertEquals("marissa.bloggs@change.org", user.getEmail()); - assertTrue(user.isVerified()); } @Test - @Disabled("SAML test doesn't compile") - void update_existingUser_if_username_different() { + @DisplayName("Can update existing user with different username but same email") + void updateExistingUserWithDifferentUsername() { Map attributeMappings = new HashMap<>(); attributeMappings.put("given_name", "firstName"); attributeMappings.put("family_name", "lastName"); @@ -545,18 +548,23 @@ void update_existingUser_if_username_different() { LinkedMultiValueMap attributes = new LinkedMultiValueMap<>(); attributes.add(GIVEN_NAME_ATTRIBUTE_NAME, "Marissa"); attributes.add(FAMILY_NAME_ATTRIBUTE_NAME, "Bloggs"); - attributes.add(EMAIL_ATTRIBUTE_NAME, "marissa.bloggs@test.com"); + attributes.add(EMAIL_ATTRIBUTE_NAME, TEST_EMAIL); attributes.add(PHONE_NUMBER_ATTRIBUTE_NAME, TEST_PHONE_NUMBER); - UaaPrincipal samlPrincipal = new UaaPrincipal(OriginKeys.NotANumber, "marissa-saml-changed", "marissa.bloggs@test.com", OriginKeys.SAML, "marissa-saml-changed", identityZoneManager.getCurrentIdentityZone().getId()); -// UaaUser user = authprovider.createIfMissing(samlPrincipal, false, new ArrayList(), attributes); + UaaPrincipal samlPrincipal = new UaaPrincipal(OriginKeys.NotANumber, + "test-changed@saml.user", TEST_EMAIL, OriginKeys.SAML, TEST_USERNAME, + identityZoneManager.getCurrentIdentityZone().getId()); + SamlUaaResponseAuthenticationConverter responseAuthenticationConverter = ((SamlLoginAuthenticationProvider) authprovider).getResponseAuthenticationConverter(); + UaaUser user = responseAuthenticationConverter.getUserManager() + .createIfMissing(samlPrincipal, false, new ArrayList(), + attributes); -// assertNotNull(user); -// assertEquals("marissa-saml-changed", user.getUsername()); + assertNotNull(user); + assertEquals("test-changed@saml.user", user.getUsername()); } @Test - void dont_update_existingUser_if_attributes_areTheSame() { + void dontUpdateExistingUserIfAttributesSame() { authenticate(); UaaUser user = userDatabase.retrieveUserByName(TEST_USERNAME, OriginKeys.SAML); @@ -567,28 +575,7 @@ void dont_update_existingUser_if_attributes_areTheSame() { } @Test - void have_attributes_changed() { - authenticate(); - assumeThat(authprovider).isInstanceOf(SamlLoginAuthenticationProvider.class); - SamlLoginAuthenticationProvider authprovider = (SamlLoginAuthenticationProvider) this.authprovider; - - UaaUser existing = userDatabase.retrieveUserByName(TEST_USERNAME, OriginKeys.SAML); - UaaUser modified = new UaaUser(new UaaUserPrototype(existing)); - assertThat(authprovider.haveUserAttributesChanged(existing, modified)).isFalse(); - modified = new UaaUser(new UaaUserPrototype(existing).withEmail("other-email")); - assertThat(authprovider.haveUserAttributesChanged(existing, modified)).as("email modified").isTrue(); - modified = new UaaUser(new UaaUserPrototype(existing).withPhoneNumber("other-phone")); - assertThat(authprovider.haveUserAttributesChanged(existing, modified)).as("Phone number modified").isTrue(); - modified = new UaaUser(new UaaUserPrototype(existing).withVerified(!existing.isVerified())); - assertThat(authprovider.haveUserAttributesChanged(existing, modified)).as("Verifiedemail modified").isTrue(); - modified = new UaaUser(new UaaUserPrototype(existing).withGivenName("other-given")); - assertThat(authprovider.haveUserAttributesChanged(existing, modified)).as("First name modified").isTrue(); - modified = new UaaUser(new UaaUserPrototype(existing).withFamilyName("other-family")); - assertThat(authprovider.haveUserAttributesChanged(existing, modified)).as("Last name modified").isTrue(); - } - - @Test - void shadowAccount_createdWith_MappedUserAttributes() { + void createShadowAccountWithMappedUserAttributes() { Map attributeMappings = new HashMap<>(); attributeMappings.put("given_name", "firstName"); attributeMappings.put("family_name", "lastName"); @@ -609,46 +596,48 @@ void shadowAccount_createdWith_MappedUserAttributes() { } @Test - @Disabled("SAML test doesn't compile") - void custom_user_attributes_stored_if_configured() { - Map attributeMappings = new HashMap<>(); - attributeMappings.put("given_name", "firstName"); - attributeMappings.put("family_name", "lastName"); - attributeMappings.put("email", "emailAddress"); - attributeMappings.put("phone_number", "phone"); - attributeMappings.put(USER_ATTRIBUTE_PREFIX + "secondary_email", "emailAddress"); - providerDefinition.setAttributeMappings(attributeMappings); + void setStoreCustomAttributesInProviderDefinitionFalse() { providerDefinition.setStoreCustomAttributes(false); provider.setConfig(providerDefinition); - provider = providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); + providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); authenticate(); - String email = "marissa@test.org"; + UaaUser user = userDatabase.retrieveUserByName(TEST_USERNAME, OriginKeys.SAML); + UserInfo userInfo = userDatabase.getUserInfo(user.getId()); + assertThat(userInfo).isNull(); + } - UaaUser user = userDatabase.retrieveUserByName(email, OriginKeys.SAML); - assertEquals("Marissa", user.getGivenName()); - assertEquals("Bloggs", user.getFamilyName()); - assertEquals(email, user.getEmail()); - assertEquals(TEST_PHONE_NUMBER, user.getPhoneNumber()); -// assertEquals("marissa.bloggs@test.com", authentication.getUserAttributes().getFirst("secondary_email")); + @Test + void setStoreCustomAttributesInProviderDefinitionTrue() { + providerDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX + "secondary_email", + "secondaryEmail"); + providerDefinition.setStoreCustomAttributes(true); + provider.setConfig(providerDefinition); + provider = providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); + authenticate(); + UaaUser user = userDatabase.retrieveUserByName(TEST_USERNAME, OriginKeys.SAML); UserInfo userInfo = userDatabase.getUserInfo(user.getId()); - assertNull(userInfo); + assertThat(userInfo).isNotNull(); + assertThat(userInfo.getUserAttributes()) + .hasSize(1) + .containsEntry("secondary_email", List.of("john.doe.secondary@example.com")); + } + @Test + void setsUserInfoRolesWhenWhiteListIsSet() { providerDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, "groups"); - providerDefinition.addWhiteListedGroup(SAML_ADMIN); providerDefinition.setStoreCustomAttributes(true); + providerDefinition.addWhiteListedGroup(SAML_ADMIN); provider.setConfig(providerDefinition); - provider = providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); + providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); authenticate(); -// assertEquals("marissa.bloggs@test.com", authentication.getUserAttributes().getFirst("secondary_email")); - userInfo = userDatabase.getUserInfo(user.getId()); - assertNotNull(userInfo); - assertEquals("marissa.bloggs@test.com", userInfo.getUserAttributes().getFirst("secondary_email")); - assertNotNull(userInfo.getRoles()); - assertEquals(1, userInfo.getRoles().size()); - assertEquals(SAML_ADMIN, userInfo.getRoles().get(0)); + UaaUser user = userDatabase.retrieveUserByName(TEST_USERNAME, OriginKeys.SAML); + UserInfo userInfo = userDatabase.getUserInfo(user.getId()); + + assertThat(userInfo).isNotNull(); + assertThat(userInfo.getRoles()).containsExactly(SAML_ADMIN); } @Test @@ -694,7 +683,7 @@ void shadowAccountNotCreated_givenShadowAccountCreationDisabled() { providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); try { -// getAuthentication(authprovider); + authenticate(); fail("Expected authentication to throw LoginSAMLException"); } catch (SamlLoginException ignored) { @@ -709,7 +698,6 @@ void shadowAccountNotCreated_givenShadowAccountCreationDisabled() { } @Test - @Disabled("SAML test doesn't compile") void should_NotCreateShadowAccount_AndInstead_UpdateExistingUserUsername_if_userWithEmailExists() { Map attributeMappings = new HashMap<>(); attributeMappings.put("email", "emailAddress"); @@ -717,33 +705,35 @@ void should_NotCreateShadowAccount_AndInstead_UpdateExistingUserUsername_if_user provider.setConfig(providerDefinition); providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); - ScimUser createdUser = createSamlUser("marissa.bloggs@test.com", identityZoneManager.getCurrentIdentityZone().getId(), userProvisioning); + ScimUser createdUser = createSamlUser(TEST_EMAIL, + identityZoneManager.getCurrentIdentityZone().getId(), userProvisioning); -// getAuthentication(authprovider); - - UaaUser uaaUser = userDatabase.retrieveUserByName("marissa-saml", OriginKeys.SAML); - assertEquals(createdUser.getId(), uaaUser.getId()); - assertEquals("marissa-saml", uaaUser.getUsername()); + authenticate(); + UaaUser uaaUser = userDatabase.retrieveUserByName(TEST_USERNAME, OriginKeys.SAML); + assertThat(uaaUser) + .returns(createdUser.getId(), UaaUser::getId) + .returns(TEST_USERNAME, UaaUser::getUsername); } @Test - @Disabled("SAML test doesn't compile") - void error_when_multipleUsers_with_sameEmail() { + void authFailsIfMultipleExistingUsersWithSameEmailExist() { Map attributeMappings = new HashMap<>(); attributeMappings.put("email", "emailAddress"); providerDefinition.setAttributeMappings(attributeMappings); provider.setConfig(providerDefinition); providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); - createSamlUser("marissa.bloggs@test.com", identityZoneManager.getCurrentIdentityZone().getId(), userProvisioning); - createSamlUser("marissa.bloggs", identityZoneManager.getCurrentIdentityZone().getId(), userProvisioning); + createSamlUser(TEST_EMAIL, identityZoneManager.getCurrentIdentityZone().getId(), + userProvisioning); + // get user by username should fail, then attempt get user by email causes exception + createSamlUser("randomUsername", identityZoneManager.getCurrentIdentityZone().getId(), + userProvisioning); -// assertThrows(IncorrectResultSizeDataAccessException.class, () -> getAuthentication(authprovider)); + assertThrows(IncorrectResultSizeDataAccessException.class, this::authenticate); } @Test - @Disabled("SAML test doesn't compile") - void shadowUser_GetsCreatedWithDefaultValues_IfAttributeNotMapped() { + void shadowUserGetsCreatedWithDefaultValuesIfAttributeNotMapped() { Map attributeMappings = new HashMap<>(); attributeMappings.put("surname", "lastName"); attributeMappings.put("email", "emailAddress"); @@ -751,143 +741,39 @@ void shadowUser_GetsCreatedWithDefaultValues_IfAttributeNotMapped() { provider.setConfig(providerDefinition); providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); -// UaaAuthentication authentication = getAuthentication(authprovider); - UaaUser user = userDatabase.retrieveUserByName("marissa-saml", OriginKeys.SAML); - assertEquals("marissa.bloggs", user.getGivenName()); - assertEquals("test.com", user.getFamilyName()); - assertEquals("marissa.bloggs@test.com", user.getEmail()); -// assertEquals(0, authentication.getUserAttributes().size(), "No custom attributes have been mapped"); + UaaAuthentication authentication = authenticate(); + UaaUser user = userDatabase.retrieveUserByName(TEST_USERNAME, OriginKeys.SAML); + + // this splits name fields from email from TestOpenSamlObjects + assertThat(user).returns("john.doe", UaaUser::getGivenName) + .returns("example.com", UaaUser::getFamilyName) + .returns(TEST_EMAIL, UaaUser::getEmail); + assertThat(authentication.getUserAttributes()) + .as("No custom attributes have been mapped") + .isEmpty(); } @Test - @Disabled("SAML test doesn't compile") void user_authentication_contains_custom_attributes() { String COST_CENTERS = COST_CENTER + "s"; String MANAGERS = MANAGER + "s"; Map attributeMappings = new HashMap<>(); - attributeMappings.put(USER_ATTRIBUTE_PREFIX + COST_CENTERS, COST_CENTER); attributeMappings.put(USER_ATTRIBUTE_PREFIX + MANAGERS, MANAGER); - providerDefinition.setAttributeMappings(attributeMappings); provider.setConfig(providerDefinition); providerProvisioning.update(provider, identityZoneManager.getCurrentIdentityZone().getId()); -// UaaAuthentication authentication = getAuthentication(authprovider); -// -// assertEquals(2, authentication.getUserAttributes().size(), "Expected two user attributes"); -// assertNotNull(authentication.getUserAttributes().get(COST_CENTERS), "Expected cost center attribute"); -// assertEquals(DENVER_CO, authentication.getUserAttributes().getFirst(COST_CENTERS)); -// -// assertNotNull(authentication.getUserAttributes().get(MANAGERS), "Expected manager attribute"); -// assertEquals(2, authentication.getUserAttributes().get(MANAGERS).size(), "Expected 2 manager attribute values"); -// assertThat(authentication.getUserAttributes().get(MANAGERS), containsInAnyOrder(JOHN_THE_SLOTH, KARI_THE_ANT_EATER)); - } - - @Test - @Disabled("SAML test fails") - void getUserByDefaultUsesTheAvailableData() { - assumeThat(authprovider).isInstanceOf(SamlLoginAuthenticationProvider.class); - SamlLoginAuthenticationProvider authprovider = (SamlLoginAuthenticationProvider) this.authprovider; - - UaaPrincipal principal = new UaaPrincipal( - UUID.randomUUID().toString(), - "user", - "user@example.com", - OriginKeys.SAML, - "user", - identityZoneManager.getCurrentIdentityZone().getId() - ); - LinkedMultiValueMap attributes = new LinkedMultiValueMap<>(); - attributes.add(EMAIL_ATTRIBUTE_NAME, "user@example.com"); - attributes.add(PHONE_NUMBER_ATTRIBUTE_NAME, "(415) 555-0111"); - attributes.add(GIVEN_NAME_ATTRIBUTE_NAME, "Jane"); - attributes.add(FAMILY_NAME_ATTRIBUTE_NAME, "Doe"); - attributes.add(EMAIL_VERIFIED_ATTRIBUTE_NAME, "true"); - - UaaUser user = authprovider.getUser(principal, attributes); - assertThat(user) - .returns("user", UaaUser::getUsername); -// .withEmail("user@example.com") -// .withPhoneNumber("(415) 555-0111") -// .withPassword("") -// .withGivenName("Jane") -// .withFamilyName("Doe") -// .withAuthorities(emptyIterable()) -// .withVerified(true) -// .withOrigin(OriginKeys.SAML) -// .withExternalId("user") -// .withZoneId(identityZoneManager.getCurrentIdentityZoneId()) - } - - @Test - @Disabled("SAML test fails") - void getUserWithoutOriginSuppliesDefaultsToLoginServer() { - assumeThat(authprovider).isInstanceOf(SamlLoginAuthenticationProvider.class); - SamlLoginAuthenticationProvider authprovider = (SamlLoginAuthenticationProvider) this.authprovider; - - UaaPrincipal principal = new UaaPrincipal( - UUID.randomUUID().toString(), - "user", - "user@example.com", - null, - "user", - identityZoneManager.getCurrentIdentityZone().getId() - ); - - LinkedMultiValueMap attributes = new LinkedMultiValueMap<>(); - UaaUser user = authprovider.getUser(principal, attributes); - assertThat(user) - .returns(OriginKeys.LOGIN_SERVER, UaaUser::getOrigin); - } - - @Test - @Disabled("SAML test fails") - void getUserWithoutVerifiedDefaultsToFalse() { - assumeThat(authprovider).isInstanceOf(SamlLoginAuthenticationProvider.class); - SamlLoginAuthenticationProvider authprovider = (SamlLoginAuthenticationProvider) this.authprovider; - - UaaPrincipal principal = new UaaPrincipal( - UUID.randomUUID().toString(), - "user", - "user@example.com", - null, - "user", - identityZoneManager.getCurrentIdentityZone().getId() - ); - - LinkedMultiValueMap attributes = new LinkedMultiValueMap<>(); - UaaUser user = authprovider.getUser(principal, attributes); - assertThat(user) - .returns(false, UaaUser::isVerified); - } - - @Test - @Disabled("SAML test fails") - void throwsIfUserNameAndEmailAreMissing() { - assumeThat(authprovider).isInstanceOf(SamlLoginAuthenticationProvider.class); - SamlLoginAuthenticationProvider authprovider = (SamlLoginAuthenticationProvider) this.authprovider; - - UaaPrincipal principal = new UaaPrincipal( - UUID.randomUUID().toString(), - null, - "user@example.com", - null, - "user", - identityZoneManager.getCurrentIdentityZone().getId() - ); - - LinkedMultiValueMap attributes = new LinkedMultiValueMap<>(); - - assertThrowsWithMessageThat( - BadCredentialsException.class, - () -> authprovider.getUser(principal, attributes), - is("Cannot determine username from credentials supplied") - ); + UaaAuthentication authentication = authenticate(); + assertThat(authentication.getUserAttributes()) + .hasSize(2) + .containsEntry(COST_CENTERS, List.of(DENVER_CO)) + .containsEntry(MANAGERS, List.of(JOHN_THE_SLOTH, KARI_THE_ANT_EATER)); } public static class CreateUserPublisher implements ApplicationEventPublisher { + final ScimUserBootstrap bootstrap; final List events = new ArrayList<>(); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlUaaUserManagerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlUaaUserManagerTest.java new file mode 100644 index 00000000000..e5961ff5a53 --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlUaaUserManagerTest.java @@ -0,0 +1,141 @@ +package org.cloudfoundry.identity.uaa.provider.saml; + +import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.user.UaaUser; +import org.cloudfoundry.identity.uaa.user.UaaUserPrototype; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.util.LinkedMultiValueMap; + +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assumptions.assumeThat; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.EMAIL_ATTRIBUTE_NAME; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.EMAIL_VERIFIED_ATTRIBUTE_NAME; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.FAMILY_NAME_ATTRIBUTE_NAME; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.GIVEN_NAME_ATTRIBUTE_NAME; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.PHONE_NUMBER_ATTRIBUTE_NAME; + +class SamlUaaUserManagerTest { + + private static final String TEST_USERNAME = "test@saml.user"; + private static final String ZONE_ID = "uaa"; + private UaaUser existing = createUaaUser(TEST_USERNAME, OriginKeys.SAML); + + private UaaUser createUaaUser(String username, String zoneId) { + return new UaaUser(username, "", "john.doe@example.com", "John", "Doe"); + } + + @Test + void haveAttributesChangedReturnsFalseForCopied() { + UaaUser modified = new UaaUser(new UaaUserPrototype(existing)); + assertThat(SamlUaaUserManager.haveUserAttributesChanged(existing, modified)).isFalse(); + } + + @Test + void haveAttributesChangedReturnsTrueForChangedEmail() { + UaaUser modified = new UaaUser(new UaaUserPrototype(existing).withEmail("other-email")); + assertThat(SamlUaaUserManager.haveUserAttributesChanged(existing, modified)).as("email modified").isTrue(); + } + + + @Test + void haveAttributesChangedReturnsTrueForChangedPhone() { + UaaUser modified = new UaaUser(new UaaUserPrototype(existing).withPhoneNumber("other-phone")); + assertThat(SamlUaaUserManager.haveUserAttributesChanged(existing, modified)).as("Phone number modified").isTrue(); + } + + @Test + void haveAttributesChangedReturnsTrueForChangedVerified() { + UaaUser modified = new UaaUser(new UaaUserPrototype(existing).withVerified(!existing.isVerified())); + assertThat(SamlUaaUserManager.haveUserAttributesChanged(existing, modified)).as("Verifiedemail modified").isTrue(); + } + + @Test + void haveAttributesChangedReturnsTrueForChangedGivenName() { + UaaUser modified = new UaaUser(new UaaUserPrototype(existing).withGivenName("other-given")); + assertThat(SamlUaaUserManager.haveUserAttributesChanged(existing, modified)).as("First name modified").isTrue(); + } + + @Test + void haveAttributesChangedReturnsTrueForChangedFamilyName() { + UaaUser modified = new UaaUser(new UaaUserPrototype(existing).withFamilyName("other-family")); + assertThat(SamlUaaUserManager.haveUserAttributesChanged(existing, modified)).as("Last name modified").isTrue(); + } + + @Test + void getUserByDefaultUsesTheAvailableData() { + SamlUaaUserManager userManager = new SamlUaaUserManager(null); + + UaaPrincipal principal = new UaaPrincipal( + UUID.randomUUID().toString(), + "user", + "user@example.com", + OriginKeys.SAML, + "user", + ZONE_ID + ); + LinkedMultiValueMap attributes = new LinkedMultiValueMap<>(); + attributes.add(EMAIL_ATTRIBUTE_NAME, "user@example.com"); + attributes.add(PHONE_NUMBER_ATTRIBUTE_NAME, "(415) 555-0111"); + attributes.add(GIVEN_NAME_ATTRIBUTE_NAME, "Jane"); + attributes.add(FAMILY_NAME_ATTRIBUTE_NAME, "Doe"); + attributes.add(EMAIL_VERIFIED_ATTRIBUTE_NAME, "true"); + + UaaUser user = userManager.getUser(principal, attributes); + assertThat(user) + .returns("user", UaaUser::getUsername) + .returns("user@example.com", UaaUser::getEmail) + .returns("(415) 555-0111", UaaUser::getPhoneNumber) + .returns("Jane", UaaUser::getGivenName) + .returns("Doe", UaaUser::getFamilyName) + .returns("", UaaUser::getPassword) + .returns(true, UaaUser::isVerified) + .returns(OriginKeys.SAML, UaaUser::getOrigin) + .returns("user", UaaUser::getExternalId) + .returns(ZONE_ID, UaaUser::getZoneId) + .returns(0, u -> u.getAuthorities().size()); + } + + @Test + void getUserWithoutVerifiedDefaultsToFalse() { + SamlUaaUserManager userManager = new SamlUaaUserManager(null); + + UaaPrincipal principal = new UaaPrincipal( + UUID.randomUUID().toString(), + "user", + "user@example.com", + null, + "user", + ZONE_ID + ); + + LinkedMultiValueMap attributes = new LinkedMultiValueMap<>(); + UaaUser user = userManager.getUser(principal, attributes); + assertThat(user).returns(false, UaaUser::isVerified); + } + + @Test + void throwsIfPrincipalUserNameAndUserAttributesEmailIsMissing() { + SamlUaaUserManager userManager = new SamlUaaUserManager(null); + + UaaPrincipal principal = new UaaPrincipal( + UUID.randomUUID().toString(), + null, + "getUser Should look at the userAttributes email, not this one!", + null, + "user", + ZONE_ID + ); + + LinkedMultiValueMap attributes = new LinkedMultiValueMap<>(); + + assertThatThrownBy(() -> userManager.getUser(principal, attributes)) + .isInstanceOf(BadCredentialsException.class) + .hasMessage("Cannot determine username from credentials supplied"); + } +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestOpenSamlObjects.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestOpenSamlObjects.java index c860240146b..22efe484fd9 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestOpenSamlObjects.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestOpenSamlObjects.java @@ -21,14 +21,20 @@ import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; import org.opensaml.core.xml.io.MarshallingException; 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.XSQName; import org.opensaml.core.xml.schema.XSString; import org.opensaml.core.xml.schema.XSURI; import org.opensaml.core.xml.schema.impl.XSAnyBuilder; +import org.opensaml.core.xml.schema.impl.XSBase64BinaryBuilder; import org.opensaml.core.xml.schema.impl.XSBooleanBuilder; +import org.opensaml.core.xml.schema.impl.XSDateTimeBuilder; import org.opensaml.core.xml.schema.impl.XSIntegerBuilder; +import org.opensaml.core.xml.schema.impl.XSQNameBuilder; import org.opensaml.core.xml.schema.impl.XSStringBuilder; import org.opensaml.core.xml.schema.impl.XSURIBuilder; import org.opensaml.saml.common.SAMLVersion; @@ -64,7 +70,9 @@ import javax.crypto.spec.SecretKeySpec; import javax.xml.namespace.QName; import java.security.cert.X509Certificate; +import java.time.Instant; import java.util.ArrayList; +import java.util.Arrays; import java.util.Base64; import java.util.List; import java.util.UUID; @@ -95,6 +103,7 @@ public final class TestOpenSamlObjects { } private TestOpenSamlObjects() { + throw new java.lang.UnsupportedOperationException("This is a utility class and cannot be instantiated"); } public static Response response() { @@ -291,92 +300,159 @@ static AttributeStatement customAttributeStatement(String attributeName, XMLObje public static List attributeStatements() { List attributeStatements = new ArrayList<>(); - AttributeStatementBuilder attributeStatementBuilder = new AttributeStatementBuilder(); - AttributeBuilder attributeBuilder = new AttributeBuilder(); - AttributeStatement attrStmt1 = attributeStatementBuilder.buildObject(); - - Attribute emailAttr = attributeBuilder.buildObject(); - emailAttr.setName("email"); - XSAny email1 = new XSAnyBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSAny.TYPE_NAME); // gh-8864 - email1.setTextContent("john.doe@example.com"); - emailAttr.getAttributeValues().add(email1); - - XSAny email2 = new XSAnyBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME); - email2.setTextContent("doe.john@example.com"); - emailAttr.getAttributeValues().add(email2); - attrStmt1.getAttributes().add(emailAttr); - - Attribute nameAttr = attributeBuilder.buildObject(); - nameAttr.setName("name"); - XSString name = new XSStringBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSString.TYPE_NAME); - name.setValue("John Doe"); - nameAttr.getAttributeValues().add(name); - attrStmt1.getAttributes().add(nameAttr); - - Attribute firstNameAttr = attributeBuilder.buildObject(); - firstNameAttr.setName("firstName"); - XSString firstName = new XSStringBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSString.TYPE_NAME); - firstName.setValue("John"); - firstNameAttr.getAttributeValues().add(firstName); - attrStmt1.getAttributes().add(firstNameAttr); - - Attribute lastNameAttr = attributeBuilder.buildObject(); - lastNameAttr.setName("lastName"); - XSString lastName = new XSStringBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSString.TYPE_NAME); - lastName.setValue("Doe"); - lastNameAttr.getAttributeValues().add(lastName); - attrStmt1.getAttributes().add(lastNameAttr); - - Attribute roleOneAttr = attributeBuilder.buildObject(); // gh-11042 - roleOneAttr.setName("role"); - XSString roleOne = new XSStringBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSString.TYPE_NAME); - roleOne.setValue("RoleOne"); - roleOneAttr.getAttributeValues().add(roleOne); - attrStmt1.getAttributes().add(roleOneAttr); - - Attribute roleTwoAttr = attributeBuilder.buildObject(); // gh-11042 - roleTwoAttr.setName("role"); - XSString roleTwo = new XSStringBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSString.TYPE_NAME); - roleTwo.setValue("RoleTwo"); - roleTwoAttr.getAttributeValues().add(roleTwo); - attrStmt1.getAttributes().add(roleTwoAttr); - - Attribute ageAttr = attributeBuilder.buildObject(); - ageAttr.setName("age"); - XSInteger age = new XSIntegerBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSInteger.TYPE_NAME); - age.setValue(21); - ageAttr.getAttributeValues().add(age); - attrStmt1.getAttributes().add(ageAttr); - - Attribute phoneAttr = attributeBuilder.buildObject(); - phoneAttr.setName("phone"); - XSString phone = new XSStringBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSString.TYPE_NAME); - phone.setValue("123-456-7890"); - phoneAttr.getAttributeValues().add(phone); - attrStmt1.getAttributes().add(phoneAttr); - - attributeStatements.add(attrStmt1); - AttributeStatement attrStmt2 = attributeStatementBuilder.buildObject(); - - Attribute websiteAttr = attributeBuilder.buildObject(); - websiteAttr.setName("website"); - XSURI uri = new XSURIBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSURI.TYPE_NAME); - uri.setURI("https://johndoe.com/"); - websiteAttr.getAttributeValues().add(uri); - attrStmt2.getAttributes().add(websiteAttr); - - Attribute registeredAttr = attributeBuilder.buildObject(); - registeredAttr.setName("registered"); - XSBoolean registered = new XSBooleanBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, - XSBoolean.TYPE_NAME); - registered.setValue(new XSBooleanValue(true, false)); - registeredAttr.getAttributeValues().add(registered); - attrStmt2.getAttributes().add(registeredAttr); - attributeStatements.add(attrStmt2); + attributeStatements.add(attributeStatement( + attributeWithAnyValues("email", "john.doe@example.com", "doe.john@example.com"), + attributeWithAnyValues("secondaryEmail", "john.doe.secondary@example.com"), + attributeWithStringValue("name", "John Doe"), + attributeWithStringValue("firstName", "John"), + attributeWithStringValue("lastName", "Doe"), + attributeWithStringValue("role", "RoleOne"), + attributeWithStringValue("role", "RoleTwo"), + attributeWithIntValue("age", 21), + attributeWithStringValue("phone", "123-456-7890"), + attributeWithAnyValues("manager", "John the Sloth", "Kari the Ant Eater"), + attributeWithAnyValues("costCenter", "Denver,CO") + )); + + attributeStatements.add(attributeStatement( + attributeWithUriValue("website", "https://johndoe.com/"), + attributeWithBooleanValue("registered", true), + attributeWithStringValue("acr", AuthnContext.PASSWORD_AUTHN_CTX), + attributeWithAnyValues("groups", "saml.admin", "saml.user", "saml.unmapped"), + attributeWithAnyValues("2ndgroups", "saml.test"), + // Ensure an empty attribute is handled properly + attributeWithNoValue(), + // Ensure an empty attribute value is handled properly + attributeWithNullValue() + )); + + // Ensure an empty attribute statement is handled properly + attributeStatements.add(attributeStatement()); + + attributeStatements.add(attributeStatement( + attributeWithUriValue("XSURI", "http://localhost:8080/someuri"), + attributeWithAnyValues("XSAny", "XSAnyValue"), + attributeWithQNameValue("XSQName", "XSQNameValue"), + attributeWithIntValue("XSInteger", 3), + attributeWithBooleanValue("XSBoolean", true), + attributeWithDateTimeValue("XSDateTime", Instant.ofEpochSecond(0)), + attributeWithBase64BinaryValue("XSBase64Binary", "00001111") + )); + return attributeStatements; } + private static AttributeStatement attributeStatement(Attribute... attributes) { + AttributeStatement attrStmt = new AttributeStatementBuilder().buildObject(); + attrStmt.getAttributes().addAll(Arrays.asList(attributes)); + return attrStmt; + } + + /** + * Attribute with a null value + */ + private static Attribute attributeWithNullValue() { + Attribute attr = new AttributeBuilder().buildObject(); + attr.setName("emptyAttributeValue"); + attr.getAttributeValues().add(null); + + return attr; + } + + /** + * Attribute with no values + */ + private static Attribute attributeWithNoValue() { + Attribute attr = new AttributeBuilder().buildObject(); + attr.setName("emptyAttribute"); + + return attr; + } + + private static Attribute attributeWithAnyValues(String attributeName, String... attribValues) { + Attribute attr = new AttributeBuilder().buildObject(); + attr.setName(attributeName); + + for (String attribValue : Arrays.asList(attribValues)) { + XSAny value = new XSAnyBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSAny.TYPE_NAME); + value.setTextContent(attribValue); + attr.getAttributeValues().add(value); + } + return attr; + } + + private static Attribute attributeWithStringValue(String attributeName, String attribValue) { + Attribute attr = new AttributeBuilder().buildObject(); + attr.setName(attributeName); + XSString value = new XSStringBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSString.TYPE_NAME); + value.setValue(attribValue); + attr.getAttributeValues().add(value); + + return attr; + } + + private static Attribute attributeWithBooleanValue(String attributeName, boolean attribValue) { + Attribute attr = new AttributeBuilder().buildObject(); + attr.setName(attributeName); + XSBoolean value = new XSBooleanBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, + XSBoolean.TYPE_NAME); + value.setValue(new XSBooleanValue(attribValue, false)); + attr.getAttributeValues().add(value); + + return attr; + } + + private static Attribute attributeWithUriValue(String attributeName, String attribValue) { + Attribute attr = new AttributeBuilder().buildObject(); + attr.setName(attributeName); + XSURI value = new XSURIBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSURI.TYPE_NAME); + value.setURI(attribValue); + attr.getAttributeValues().add(value); + + return attr; + } + + private static Attribute attributeWithIntValue(String attributeName, int attribValue) { + Attribute attr = new AttributeBuilder().buildObject(); + attr.setName(attributeName); + XSInteger value = new XSIntegerBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSInteger.TYPE_NAME); + value.setValue(attribValue); + attr.getAttributeValues().add(value); + + return attr; + } + + private static Attribute attributeWithQNameValue(String attributeName, String attribValue) { + Attribute attr = new AttributeBuilder().buildObject(); + attr.setName(attributeName); + XSQName value = new XSQNameBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSQName.TYPE_NAME); + value.setValue(QName.valueOf(attribValue)); + attr.getAttributeValues().add(value); + + return attr; + } + + private static Attribute attributeWithBase64BinaryValue(String attributeName, String attribValue) { + Attribute attr = new AttributeBuilder().buildObject(); + attr.setName(attributeName); + XSBase64Binary value = new XSBase64BinaryBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSBase64Binary.TYPE_NAME); + value.setValue(attribValue); + attr.getAttributeValues().add(value); + + return attr; + } + + private static Attribute attributeWithDateTimeValue(String attributeName, Instant attribValue) { + Attribute attr = new AttributeBuilder().buildObject(); + attr.setName(attributeName); + XSDateTime value = new XSDateTimeBuilder().buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSDateTime.TYPE_NAME); + value.setValue(attribValue); + attr.getAttributeValues().add(value); + + return attr; + } + static Status successStatus() { return status(StatusCode.SUCCESS); } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestRelyingPartyRegistrations.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestRelyingPartyRegistrations.java index d7d7dbd3997..9f7014f4f3b 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestRelyingPartyRegistrations.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestRelyingPartyRegistrations.java @@ -30,6 +30,7 @@ public final class TestRelyingPartyRegistrations { private TestRelyingPartyRegistrations() { + throw new java.lang.UnsupportedOperationException("This is a utility class and cannot be instantiated"); } public static RelyingPartyRegistration.Builder relyingPartyRegistration() { diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestSaml2X509Credentials.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestSaml2X509Credentials.java index 3ec8ccc717a..f0e9b9fc432 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestSaml2X509Credentials.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/TestSaml2X509Credentials.java @@ -42,6 +42,7 @@ public final class TestSaml2X509Credentials { private TestSaml2X509Credentials() { + throw new java.lang.UnsupportedOperationException("This is a utility class and cannot be instantiated"); } public static Saml2X509Credential assertingPartySigningCredential() { diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlTestUtils.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlTestUtils.java index 52cb4626064..7a4a529bf48 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlTestUtils.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/idp/SamlTestUtils.java @@ -30,6 +30,10 @@ // Attempt to move usages to Saml2TestUtils style public class SamlTestUtils { + private SamlTestUtils() { + throw new java.lang.UnsupportedOperationException("This is a utility class and cannot be instantiated"); + } + public static final String PROVIDER_PRIVATE_KEY_PASSWORD = "password"; public static final String PROVIDER_PRIVATE_KEY = """ diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LoginServerSecurityIntegrationTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LoginServerSecurityIntegrationTests.java index fc734c90c5d..ab266a1ef7c 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LoginServerSecurityIntegrationTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LoginServerSecurityIntegrationTests.java @@ -23,6 +23,7 @@ import org.cloudfoundry.identity.uaa.oauth.client.test.BeforeOAuth2Context; import org.cloudfoundry.identity.uaa.oauth.client.test.OAuth2ContextConfiguration; import org.cloudfoundry.identity.uaa.oauth.client.test.OAuth2ContextSetup; +import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.test.TestAccountSetup; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; @@ -37,17 +38,17 @@ import org.springframework.http.ResponseEntity; import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; -import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; import org.springframework.web.client.RestOperations; import org.springframework.web.client.RestTemplate; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.Map; +import static org.assertj.core.api.Assertions.assertThat; import static org.cloudfoundry.identity.uaa.constants.OriginKeys.LOGIN_SERVER; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -68,13 +69,10 @@ public class LoginServerSecurityIntegrationTests { private final String LOGIN_SERVER_JOE = "ls_joe" + new RandomValueStringGenerator().generate().toLowerCase(); private final String userEndpoint = "/Users"; - - private ScimUser joe; - @Rule public ServerRunning serverRunning = ServerRunning.isRunning(); - - private UaaTestAccounts testAccounts = UaaTestAccounts.standard(serverRunning); + private ScimUser joe; + private final UaaTestAccounts testAccounts = UaaTestAccounts.standard(serverRunning); @Rule public TestAccountSetup testAccountSetup = TestAccountSetup.standard(serverRunning, testAccounts); @@ -82,9 +80,9 @@ public class LoginServerSecurityIntegrationTests { @Rule public OAuth2ContextSetup context = OAuth2ContextSetup.withTestAccounts(serverRunning, testAccounts); - private MultiValueMap params = new LinkedMultiValueMap(); + private final MultiValueMap params = new LinkedMultiValueMap(); - private HttpHeaders headers = new HttpHeaders(); + private final HttpHeaders headers = new HttpHeaders(); private ScimUser userForLoginServer; @Before @@ -92,13 +90,13 @@ public void init() { params.set("source", "login"); params.set("redirect_uri", "http://localhost:8080/app/"); params.set("response_type", "token"); - if (joe!=null) { + if (joe != null) { params.set("username", joe.getUserName()); } headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - ((RestTemplate)serverRunning.getRestTemplate()).setErrorHandler(new OAuth2ErrorHandler(context.getResource()) { + ((RestTemplate) serverRunning.getRestTemplate()).setErrorHandler(new OAuth2ErrorHandler(context.getResource()) { // Pass errors through in response entity for status code analysis @Override public boolean hasError(ClientHttpResponse response) { @@ -131,28 +129,27 @@ public void setUpUserAccounts() { userForLoginServer.setVerified(true); userForLoginServer.setOrigin(LOGIN_SERVER); - ResponseEntity newuser = client.postForEntity(serverRunning.getUrl(userEndpoint), user, - ScimUser.class); + ResponseEntity newuser = client.postForEntity(serverRunning.getUrl(userEndpoint), user, ScimUser.class); userForLoginServer = client.postForEntity(serverRunning.getUrl(userEndpoint), userForLoginServer, ScimUser.class).getBody(); joe = newuser.getBody(); - assertEquals(JOE, joe.getUserName()); + assertThat(joe.getUserName()).isEqualTo(JOE); PasswordChangeRequest change = new PasswordChangeRequest(); change.setPassword("Passwo3d"); HttpHeaders headers = new HttpHeaders(); ResponseEntity result = client - .exchange(serverRunning.getUrl(userEndpoint) + "/{id}/password", - HttpMethod.PUT, new HttpEntity(change, headers), - Void.class, joe.getId()); + .exchange(serverRunning.getUrl(userEndpoint) + "/{id}/password", + HttpMethod.PUT, new HttpEntity(change, headers), + Void.class, joe.getId()); assertEquals(HttpStatus.OK, result.getStatusCode()); // The implicit grant for cf requires extra parameters in the // authorization request context.setParameters(Collections.singletonMap("credentials", - testAccounts.getJsonCredentials(joe.getUserName(), "Passwo3d"))); + testAccounts.getJsonCredentials(joe.getUserName(), "Passwo3d"))); } @@ -165,7 +162,7 @@ public void testAuthenticateReturnsUserID() { assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(JOE, response.getBody().get("username")); assertEquals(OriginKeys.UAA, response.getBody().get(OriginKeys.ORIGIN)); - assertTrue(StringUtils.hasText((String)response.getBody().get("user_id"))); + assertTrue(StringUtils.hasText((String) response.getBody().get("user_id"))); } @Test @@ -177,7 +174,7 @@ public void testAuthenticateMarissaReturnsUserID() { assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals("marissa", response.getBody().get("username")); assertEquals(OriginKeys.UAA, response.getBody().get(OriginKeys.ORIGIN)); - assertTrue(StringUtils.hasText((String)response.getBody().get("user_id"))); + assertTrue(StringUtils.hasText((String) response.getBody().get("user_id"))); } @Test @@ -235,6 +232,7 @@ public void testLoginServerCanAuthenticateUserForAuthorizationCode() { // The approval page messaging response assertNotNull("There should be scopes: " + results, results.get("scopes")); } + @Test @OAuth2ContextConfiguration(LoginClient.class) public void testLoginServerCanAuthenticateUserWithIDForAuthorizationCode() { @@ -269,7 +267,7 @@ public void testMissingUserInfoIsError() { @OAuth2ContextConfiguration(LoginClient.class) public void testMissingUsernameIsError() { ((RestTemplate) serverRunning.getRestTemplate()) - .setRequestFactory(new HttpComponentsClientHttpRequestFactory()); + .setRequestFactory(new HttpComponentsClientHttpRequestFactory()); params.set("client_id", testAccounts.getDefaultImplicitResource().getClientId()); params.remove("username"); // Some of the user info is there but not enough to determine a username @@ -287,7 +285,7 @@ public void testMissingUsernameIsError() { public void testWrongUsernameIsErrorAddNewEnabled() { ((RestTemplate) serverRunning.getRestTemplate()) - .setRequestFactory(new HttpComponentsClientHttpRequestFactory()); + .setRequestFactory(new HttpComponentsClientHttpRequestFactory()); ImplicitResourceDetails resource = testAccounts.getDefaultImplicitResource(); params.set("client_id", resource.getClientId()); @@ -310,7 +308,7 @@ public void testWrongUsernameIsErrorAddNewEnabled() { public void testWrongUsernameIsErrorAddNewDisabled() { ((RestTemplate) serverRunning.getRestTemplate()) - .setRequestFactory(new HttpComponentsClientHttpRequestFactory()); + .setRequestFactory(new HttpComponentsClientHttpRequestFactory()); ImplicitResourceDetails resource = testAccounts.getDefaultImplicitResource(); params.set("client_id", resource.getClientId()); @@ -332,9 +330,9 @@ public void testWrongUsernameIsErrorAddNewDisabled() { @OAuth2ContextConfiguration(LoginClient.class) public void testAddNewUserWithWrongEmailFormat() { ((RestTemplate) serverRunning.getRestTemplate()) - .setRequestFactory(new HttpComponentsClientHttpRequestFactory()); + .setRequestFactory(new HttpComponentsClientHttpRequestFactory()); params.set("client_id", testAccounts.getDefaultImplicitResource().getClientId()); - params.set("source","login"); + params.set("source", "login"); params.set("username", "newuser"); params.remove("given_name"); params.remove("family_name"); @@ -357,10 +355,10 @@ public void testAddNewUserWithWrongEmailFormat() { public void testLoginServerCfPasswordToken() { ImplicitResourceDetails resource = testAccounts.getDefaultImplicitResource(); HttpHeaders headers = new HttpHeaders(); - headers.add("Accept",MediaType.APPLICATION_JSON_VALUE); + headers.add("Accept", MediaType.APPLICATION_JSON_VALUE); params.set("client_id", resource.getClientId()); - params.set("client_secret",""); - params.set("source","login"); + params.set("client_secret", ""); + params.set("source", "login"); params.set("username", userForLoginServer.getUserName()); params.set(OriginKeys.ORIGIN, userForLoginServer.getOrigin()); params.set(UaaAuthenticationDetails.ADD_NEW, "false"); @@ -382,11 +380,11 @@ public void testLoginServerCfPasswordToken() { public void testLoginServerWithoutBearerToken() { ImplicitResourceDetails resource = testAccounts.getDefaultImplicitResource(); HttpHeaders headers = new HttpHeaders(); - headers.add("Accept",MediaType.APPLICATION_JSON_VALUE); + headers.add("Accept", MediaType.APPLICATION_JSON_VALUE); headers.add("Authorization", getAuthorizationEncodedValue(resource.getClientId(), "")); params.set("client_id", resource.getClientId()); - params.set("client_secret",""); - params.set("source","login"); + params.set("client_secret", ""); + params.set("source", "login"); params.set(UaaAuthenticationDetails.ADD_NEW, "false"); params.set("grant_type", "password"); String redirect = resource.getPreEstablishedRedirectUri(); @@ -403,10 +401,10 @@ public void testLoginServerWithoutBearerToken() { public void testLoginServerCfInvalidClientPasswordToken() { ImplicitResourceDetails resource = testAccounts.getDefaultImplicitResource(); HttpHeaders headers = new HttpHeaders(); - headers.add("Accept",MediaType.APPLICATION_JSON_VALUE); + headers.add("Accept", MediaType.APPLICATION_JSON_VALUE); params.set("client_id", resource.getClientId()); - params.set("client_secret","bogus"); - params.set("source","login"); + params.set("client_secret", "bogus"); + params.set("source", "login"); params.set(UaaAuthenticationDetails.ADD_NEW, "false"); params.set("grant_type", "password"); @@ -417,7 +415,7 @@ public void testLoginServerCfInvalidClientPasswordToken() { @SuppressWarnings("rawtypes") ResponseEntity response = serverRunning.postForMap(serverRunning.getAccessTokenUri(), params, headers); HttpStatus statusCode = response.getStatusCode(); - assertTrue("Status code should be 401 or 403.", statusCode==HttpStatus.FORBIDDEN || statusCode==HttpStatus.UNAUTHORIZED); + assertTrue("Status code should be 401 or 403.", statusCode == HttpStatus.FORBIDDEN || statusCode == HttpStatus.UNAUTHORIZED); } @Test @@ -425,10 +423,10 @@ public void testLoginServerCfInvalidClientPasswordToken() { public void testLoginServerCfInvalidClientToken() { ImplicitResourceDetails resource = testAccounts.getDefaultImplicitResource(); HttpHeaders headers = new HttpHeaders(); - headers.add("Accept",MediaType.APPLICATION_JSON_VALUE); + headers.add("Accept", MediaType.APPLICATION_JSON_VALUE); params.set("client_id", resource.getClientId()); - params.set("client_secret","bogus"); - params.set("source","login"); + params.set("client_secret", "bogus"); + params.set("source", "login"); params.set(UaaAuthenticationDetails.ADD_NEW, "false"); params.set("grant_type", "password"); @@ -440,13 +438,13 @@ public void testLoginServerCfInvalidClientToken() { ResponseEntity response = serverRunning.postForMap(serverRunning.getAccessTokenUri(), params, headers); HttpStatus statusCode = response.getStatusCode(); - assertTrue("Status code should be 401 or 403.", statusCode==HttpStatus.FORBIDDEN || statusCode==HttpStatus.UNAUTHORIZED); + assertTrue("Status code should be 401 or 403.", statusCode == HttpStatus.FORBIDDEN || statusCode == HttpStatus.UNAUTHORIZED); } private String getAuthorizationEncodedValue(String username, String password) { String auth = username + ":" + password; - byte[] encodedAuth = Base64.encodeBase64(auth.getBytes(Charset.forName("US-ASCII"))); - return "Basic " + new String( encodedAuth ); + byte[] encodedAuth = Base64.encodeBase64(auth.getBytes(StandardCharsets.US_ASCII)); + return "Basic " + new String(encodedAuth); } @@ -455,7 +453,7 @@ private static class LoginClient extends ClientCredentialsResourceDetails { public LoginClient(Object target) { LoginServerSecurityIntegrationTests test = (LoginServerSecurityIntegrationTests) target; ClientCredentialsResourceDetails resource = test.testAccounts.getClientCredentialsResource( - new String[] {"oauth.login"}, "login", "loginsecret"); + new String[]{"oauth.login"}, "login", "loginsecret"); setClientId(resource.getClientId()); setClientSecret(resource.getClientSecret()); setId(getClientId()); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/TokenEndpointDocs.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/TokenEndpointDocs.java index 6f3ccd5865f..44fb7ecbebe 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/TokenEndpointDocs.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/TokenEndpointDocs.java @@ -1,49 +1,25 @@ package org.cloudfoundry.identity.uaa.login; -import java.net.URI; -import java.util.Collections; - -import org.cloudfoundry.identity.uaa.client.UaaClientDetails; -import org.cloudfoundry.identity.uaa.oauth.common.OAuth2RefreshToken; -import org.cloudfoundry.identity.uaa.util.AlphanumericRandomValueStringGenerator; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.mock.web.MockHttpSession; -import org.springframework.restdocs.ManualRestDocumentation; -import org.springframework.restdocs.headers.HeaderDescriptor; -import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; -import org.springframework.restdocs.payload.FieldDescriptor; -import org.springframework.restdocs.request.ParameterDescriptor; -import org.springframework.restdocs.snippet.Snippet; -import org.springframework.security.web.FilterChainProxy; -import org.springframework.security.web.context.HttpSessionSecurityContextRepository; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.util.UriComponents; -import org.springframework.web.util.UriComponentsBuilder; - import org.apache.commons.codec.binary.Base64; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; +import org.cloudfoundry.identity.uaa.client.UaaClientDetails; import org.cloudfoundry.identity.uaa.mock.token.AbstractTokenMockMvcTests; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; +import org.cloudfoundry.identity.uaa.oauth.common.OAuth2RefreshToken; import org.cloudfoundry.identity.uaa.oauth.jwt.JwtClientAuthentication; import org.cloudfoundry.identity.uaa.oauth.pkce.PkceValidationService; import org.cloudfoundry.identity.uaa.oauth.token.CompositeToken; import org.cloudfoundry.identity.uaa.oauth.token.TokenConstants; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.provider.saml.idp.SamlTestUtils; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.test.JUnitRestDocumentationExtension; import org.cloudfoundry.identity.uaa.test.SnippetUtils; import org.cloudfoundry.identity.uaa.test.TestClient; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; import org.cloudfoundry.identity.uaa.user.UaaAuthority; +import org.cloudfoundry.identity.uaa.util.AlphanumericRandomValueStringGenerator; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.IdentityZoneSwitchingFilter; @@ -51,11 +27,38 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -//import org.opensaml.saml2.core.NameID; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockHttpSession; +import org.springframework.restdocs.ManualRestDocumentation; +import org.springframework.restdocs.headers.HeaderDescriptor; +import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; +import org.springframework.restdocs.payload.FieldDescriptor; +import org.springframework.restdocs.request.ParameterDescriptor; +import org.springframework.restdocs.snippet.Snippet; +import org.springframework.security.web.FilterChainProxy; +import org.springframework.security.web.context.HttpSessionSecurityContextRepository; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; + +import java.net.URI; +import java.util.Collections; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.MockSecurityContext; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.getClientCredentialsOAuthAccessToken; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.getUserOAuthAccessToken; +import static org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils.CLIENT_ID; +import static org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils.GRANT_TYPE; +import static org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils.REDIRECT_URI; +import static org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils.RESPONSE_TYPE; +import static org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils.SCOPE; +import static org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils.STATE; import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_AUTHORIZATION_CODE; import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_CLIENT_CREDENTIALS; import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_PASSWORD; @@ -68,7 +71,6 @@ import static org.cloudfoundry.identity.uaa.provider.saml.idp.SamlTestUtils.createLocalSamlIdpDefinition; import static org.cloudfoundry.identity.uaa.test.SnippetUtils.parameterWithName; import static org.hamcrest.Matchers.containsString; -import static org.junit.Assert.fail; import static org.springframework.http.HttpHeaders.AUTHORIZATION; import static org.springframework.http.HttpHeaders.HOST; import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED; @@ -88,12 +90,6 @@ import static org.springframework.restdocs.request.RequestDocumentation.requestParameters; import static org.springframework.restdocs.snippet.Attributes.key; import static org.springframework.restdocs.templates.TemplateFormats.markdown; -import static org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils.CLIENT_ID; -import static org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils.GRANT_TYPE; -import static org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils.REDIRECT_URI; -import static org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils.RESPONSE_TYPE; -import static org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils.SCOPE; -import static org.cloudfoundry.identity.uaa.oauth.common.util.OAuth2Utils.STATE; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -399,8 +395,7 @@ void getTokenUsingUserTokenGrant() throws Exception { @Test @Disabled("SAML test doesn't compile") void getTokenUsingSaml2BearerGrant() throws Exception { - SamlTestUtils samlTestUtils = new SamlTestUtils(); -// samlTestUtils.initializeSimple(); +// SamlTestUtils.initializeSimple(); final String subdomain = "68uexx"; //all our SAML defaults use :8080/uaa/ so we have to use that here too @@ -835,13 +830,13 @@ void revokeAllTokens_forAUser() throws Exception { mockMvc.perform(get - .header("Authorization", "Bearer " + adminToken)) + .header("Authorization", "Bearer " + adminToken)) .andExpect(status().isOk()) .andDo(document("{ClassName}/{methodName}", preprocessResponse(prettyPrint()), requestHeaders, pathParameters)); mockMvc.perform( - get("/oauth/clients") - .header("Authorization", "Bearer " + userInfoToken)) + get("/oauth/clients") + .header("Authorization", "Bearer " + userInfoToken)) .andExpect(status().isUnauthorized()) .andExpect(content().string(containsString("\"error\":\"invalid_token\""))); } @@ -892,26 +887,26 @@ void revokeAllTokens_forAUserClientCombination() throws Exception { ); mockMvc.perform( - get("/userinfo") - .header("Authorization", "Bearer " + userInfoTokenToRevoke)) + get("/userinfo") + .header("Authorization", "Bearer " + userInfoTokenToRevoke)) .andExpect(status().isOk()); MockHttpServletRequestBuilder get = RestDocumentationRequestBuilders.get("/oauth/token/revoke/user/{userId}/client/{clientId}", user.getId(), client.getClientId()); mockMvc.perform(get - .header("Authorization", "Bearer " + adminToken)) + .header("Authorization", "Bearer " + adminToken)) .andExpect(status().isOk()) .andDo(document("{ClassName}/{methodName}", preprocessResponse(prettyPrint()), requestHeaders, pathParameters)); mockMvc.perform( - get("/userinfo") - .header("Authorization", "Bearer " + userInfoTokenToRevoke)) + get("/userinfo") + .header("Authorization", "Bearer " + userInfoTokenToRevoke)) .andExpect(status().isUnauthorized()) .andExpect(content().string(containsString("\"error\":\"invalid_token\""))); mockMvc.perform( - get("/userinfo") - .header("Authorization", "Bearer " + userInfoTokenToRemainValid)) + get("/userinfo") + .header("Authorization", "Bearer " + userInfoTokenToRemainValid)) .andExpect(status().isOk()); } @@ -943,13 +938,13 @@ void revokeAllTokens_forAClient() throws Exception { Snippet pathParameters = pathParameters(parameterWithName("clientId").description("The id of the client")); MockHttpServletRequestBuilder get = RestDocumentationRequestBuilders.get("/oauth/token/revoke/client/{clientId}", client.getClientId()); mockMvc.perform(get - .header("Authorization", "Bearer " + adminToken)) + .header("Authorization", "Bearer " + adminToken)) .andExpect(status().isOk()) .andDo(document("{ClassName}/{methodName}", preprocessResponse(prettyPrint()), requestHeaders, pathParameters)); mockMvc.perform( - get("/oauth/clients") - .header("Authorization", "Bearer " + readClientsToken)) + get("/oauth/clients") + .header("Authorization", "Bearer " + readClientsToken)) .andExpect(status().isUnauthorized()) .andExpect(content().string(containsString("\"error\":\"invalid_token\""))); } @@ -997,7 +992,7 @@ void revokeSingleToken() throws Exception { MockHttpServletRequestBuilder delete = RestDocumentationRequestBuilders.delete("/oauth/token/revoke/{tokenId}", userInfoToken); mockMvc.perform(delete - .header(HttpHeaders.AUTHORIZATION, "Bearer " + userInfoToken)) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + userInfoToken)) .andExpect(status().isOk()) .andDo(document("{ClassName}/{methodName}", preprocessResponse(prettyPrint()), requestHeaders, pathParameters)); } @@ -1035,9 +1030,9 @@ void listTokens_client() throws Exception { MockHttpServletRequestBuilder get = RestDocumentationRequestBuilders.get("/oauth/token/list/client/{clientId}", client.getClientId()); mockMvc.perform( - get - .header(HttpHeaders.AUTHORIZATION, "Bearer " + clientToken) - .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)) + get + .header(HttpHeaders.AUTHORIZATION, "Bearer " + clientToken) + .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)) .andExpect(status().isOk()) .andDo(document("{ClassName}/{methodName}", preprocessResponse(prettyPrint()), requestHeaders, pathParameters, listTokenResponseFields)); } @@ -1086,9 +1081,9 @@ void listTokens_user() throws Exception { MockHttpServletRequestBuilder get = RestDocumentationRequestBuilders.get("/oauth/token/list/user/{userId}", user.getId()); mockMvc.perform( - get - .header(HttpHeaders.AUTHORIZATION, "Bearer " + clientToken) - .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)) + get + .header(HttpHeaders.AUTHORIZATION, "Bearer " + clientToken) + .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)) .andExpect(status().isOk()) .andDo(document("{ClassName}/{methodName}", preprocessResponse(prettyPrint()), requestHeaders, pathParameters, listTokenResponseFields)); } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/saml/SamlAuthenticationMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/saml/SamlAuthenticationMockMvcTests.java index 1f2b622ef40..153722a5bf0 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/saml/SamlAuthenticationMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/saml/SamlAuthenticationMockMvcTests.java @@ -40,7 +40,6 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java index 2cf5b7c75a5..9c1e5e567d6 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java @@ -134,6 +134,10 @@ public final class MockMvcUtils { + private MockMvcUtils() { + throw new java.lang.UnsupportedOperationException("This is a utility class and cannot be instantiated"); + } + public static final String IDP_META_DATA = "\n" + "\n" + @@ -167,9 +171,6 @@ public final class MockMvcUtils { " \n" + ""; - private MockMvcUtils() { - } - public static T getEventOfType(ArgumentCaptor captor, Class type) { for (AbstractUaaEvent event : captor.getAllValues()) { if (event.getClass().equals(type)) {