Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -72,33 +72,82 @@ public Authentication authenticate(Authentication authentication) throws Authent
UaaLoginHint uaaLoginHint = zoneAwareAuthzAuthenticationManager.extractLoginHint(authentication);
List<String> allowedProviders = getAllowedProviders();
String defaultProvider = IdentityZoneHolder.get().getConfig().getDefaultIdentityProvider();
UaaLoginHint loginHintToUse;

// check whether there is a single OIDC IdP that qualifies for the login hint (or default origin as fallback)
IdentityProvider<OIDCIdentityProviderDefinition> identityProvider = retrieveOidcPasswordIdp(uaaLoginHint, defaultProvider, allowedProviders);
List<String> possibleProviders;

// determine the possible providers, i.e., those allowed for the client, supporting password grant and active
final List<String> possibleProviders;
if (identityProvider != null) {
possibleProviders = List.of(identityProvider.getOriginKey());
} else {
List<String> identityProviders = identityProviderProvisioning.retrieveActive(IdentityZoneHolder.get().getId()).stream().filter(this::providerSupportsPasswordGrant).map(IdentityProvider::getOriginKey).toList();
possibleProviders = Optional.ofNullable(allowedProviders).orElse(identityProviders).stream().filter(identityProviders::contains).toList();
/* no suiting OIDC IdP was found - get all qualifying IdPs in the zone
* (i.e., active, supports password grant and is allowed by the client) */
final List<String> identityProviders;

final String originLoginHint = Optional.ofNullable(uaaLoginHint).map(UaaLoginHint::getOrigin).orElse(null);
final boolean isLoginHintUaa = OriginKeys.UAA.equalsIgnoreCase(originLoginHint);
if (isLoginHintUaa || OriginKeys.LDAP.equalsIgnoreCase(originLoginHint)) {
/* if "uaa" or "ldap" is passed in the login hint (not as default origin), only look up the single IdP
* instead of all qualifying ones in the zone (we later only allow this exact IdP anyway) */

// only returns active IdP
final IdentityProvider uaaOrLdapIdp = identityProviderProvisioning.retrieveByOrigin(
isLoginHintUaa ? OriginKeys.UAA : OriginKeys.LDAP,
IdentityZoneHolder.get().getId()
);

identityProviders = Optional.ofNullable(uaaOrLdapIdp)
.filter(PasswordGrantAuthenticationManager::providerSupportsPasswordGrant) // always true for "uaa" or "ldap" IdPs
.map(IdentityProvider::getOriginKey)
.stream()
.toList();
} else {
identityProviders = identityProviderProvisioning.retrieveActive(IdentityZoneHolder.get().getId())
.stream()
.filter(PasswordGrantAuthenticationManager::providerSupportsPasswordGrant)
.map(IdentityProvider::getOriginKey)
.toList();
}

// only keep the IdPs that are allowed by the client
if (allowedProviders == null) {
// client allows all IdPs
possibleProviders = new ArrayList<>(identityProviders);
} else {
possibleProviders = allowedProviders.stream().filter(identityProviders::contains).toList();
}
}
if (uaaLoginHint == null) {

// determine the IdP to use from the list of possible ones
UaaLoginHint loginHintToUse;
if (uaaLoginHint == null || uaaLoginHint.getOrigin() == null) {
if (defaultProvider != null && possibleProviders.contains(defaultProvider)) {
// no login hint was passed, but the default provider qualifies
loginHintToUse = new UaaLoginHint(defaultProvider);
} else {
/* no login hint was passed and there is either no default IdP or it does not qualify
* -> select a different IdP from the list of possible ones */
loginHintToUse = getUaaLoginHintForChainedAuth(possibleProviders);
if (identityProvider == null) {
identityProvider = retrieveOidcPasswordIdp(loginHintToUse, null, null);
}
}
} else {
/* there is a login hint passed in the password grant request
* -> we must use exactly this IdP, but only if it is in the list of possible IdPs */

if (possibleProviders.contains(uaaLoginHint.getOrigin())) {
loginHintToUse = uaaLoginHint;
} else if (allowedProviders == null || allowedProviders.contains(uaaLoginHint.getOrigin())){
} else if (allowedProviders == null || allowedProviders.contains(uaaLoginHint.getOrigin())) {
// login with the provider is not possible because it is not active or does not support password grant
throw new ProviderConfigurationException("The origin provided in the login_hint does not match an active Identity Provider, that supports password grant.");
} else {
// login with the provider is not possible because it is not allowed
throw new ProviderConfigurationException("Client is not authorized for specified user's identity provider.");
}
}

if (loginHintToUse != null) {
zoneAwareAuthzAuthenticationManager.setLoginHint(authentication, loginHintToUse);
}
Expand All @@ -110,19 +159,22 @@ public Authentication authenticate(Authentication authentication) throws Authent
}

private IdentityProvider<OIDCIdentityProviderDefinition> retrieveOidcPasswordIdp(UaaLoginHint loginHint, String defaultOrigin, List<String> allowedProviders) {
IdentityProvider<OIDCIdentityProviderDefinition> idp = null;
String useOrigin = loginHint != null && loginHint.getOrigin() != null ? loginHint.getOrigin() : defaultOrigin;
if (useOrigin != null && !useOrigin.equalsIgnoreCase(OriginKeys.UAA) && !useOrigin.equalsIgnoreCase(OriginKeys.LDAP)) {
try {
IdentityProvider<OIDCIdentityProviderDefinition> retrievedByOrigin = externalOAuthProviderProvisioning.retrieveByOrigin(useOrigin,
IdentityZoneHolder.get().getId());
if (retrievedByOrigin != null && retrievedByOrigin.isActive() && retrievedByOrigin.getOriginKey().equals(useOrigin)
&& providerSupportsPasswordGrant(retrievedByOrigin) && (allowedProviders == null || allowedProviders.contains(useOrigin))) {
idp = retrievedByOrigin;
}
} catch (EmptyResultDataAccessException e) {
// ignore

if (useOrigin == null || useOrigin.equalsIgnoreCase(OriginKeys.UAA) || useOrigin.equalsIgnoreCase(OriginKeys.LDAP)) {
return null;
}

IdentityProvider<OIDCIdentityProviderDefinition> idp = null;
try {
IdentityProvider<OIDCIdentityProviderDefinition> retrievedByOrigin = externalOAuthProviderProvisioning.retrieveByOrigin(useOrigin,
IdentityZoneHolder.get().getId());
if (retrievedByOrigin != null && retrievedByOrigin.isActive() && retrievedByOrigin.getOriginKey().equals(useOrigin)
&& providerSupportsPasswordGrant(retrievedByOrigin) && (allowedProviders == null || allowedProviders.contains(useOrigin))) {
idp = retrievedByOrigin;
}
} catch (EmptyResultDataAccessException e) {
// ignore
}
return idp;
}
Expand Down Expand Up @@ -246,7 +298,7 @@ Authentication oidcPasswordGrant(Authentication authentication, final IdentityPr
return externalOAuthAuthenticationManager.authenticate(token);
}

private boolean providerSupportsPasswordGrant(IdentityProvider provider) {
private static boolean providerSupportsPasswordGrant(IdentityProvider provider) {
if (OriginKeys.UAA.equals(provider.getType()) || OriginKeys.LDAP.equals(provider.getType())) {
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,12 @@
import org.cloudfoundry.identity.uaa.util.AlphanumericRandomValueStringGenerator;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.ArgumentCaptor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.ParameterizedTypeReference;
Expand Down Expand Up @@ -117,6 +120,8 @@ void setUp() throws Exception {

when(identityProviderProvisioning.retrieveActive("uaa")).thenReturn(Arrays.asList(idp, uaaProvider, ldapProvider));
when(externalOAuthProviderConfigurator.retrieveByOrigin("oidcprovider", "uaa")).thenReturn(idp);
when(identityProviderProvisioning.retrieveByOrigin("uaa", "uaa")).thenReturn(uaaProvider);
when(identityProviderProvisioning.retrieveByOrigin("ldap", "uaa")).thenReturn(ldapProvider);

Authentication clientAuth = mock(Authentication.class);
when(clientAuth.getName()).thenReturn("clientid");
Expand Down Expand Up @@ -630,22 +635,29 @@ void testUaaPasswordGrant_defaultProviderUaa() {
verify(zoneAwareAuthzAuthenticationManager, times(0)).setLoginHint(any(), any());
}

@Test
void testPasswordGrant_NoLoginHintWithDefaultUaa() {
@ParameterizedTest
@ValueSource(strings = { OriginKeys.UAA, OriginKeys.LDAP })
void testPasswordGrant_NoLoginHintWithDefaultUaaOrLdap(final String loginHintOrigin) {
Authentication auth = mock(Authentication.class);
when(zoneAwareAuthzAuthenticationManager.extractLoginHint(auth)).thenReturn(null);
Map<String, Object> additionalInformation = new HashMap<>();
additionalInformation.put(ClientConstants.ALLOWED_PROVIDERS, Collections.singletonList("uaa"));
additionalInformation.put(ClientConstants.ALLOWED_PROVIDERS, Collections.singletonList(loginHintOrigin));
when(uaaClient.getAdditionalInformation()).thenReturn(additionalInformation);
IdentityZoneHolder.get().getConfig().setDefaultIdentityProvider("uaa");
IdentityZoneHolder.get().getConfig().setDefaultIdentityProvider(loginHintOrigin);

instance.authenticate(auth);

/* should read all in the zone during lookup of possible providers
* - "uaa" or "ldap" is used, but not as login hint */
final String idzId = IdentityZoneHolder.get().getId();
verify(identityProviderProvisioning, times(1)).retrieveActive(idzId);
verify(identityProviderProvisioning, times(0)).retrieveByOrigin(loginHintOrigin, idzId);

verify(zoneAwareAuthzAuthenticationManager, times(1)).authenticate(auth);
ArgumentCaptor<UaaLoginHint> captor = ArgumentCaptor.forClass(UaaLoginHint.class);
verify(zoneAwareAuthzAuthenticationManager, times(1)).setLoginHint(eq(auth), captor.capture());
assertNotNull(captor.getValue());
assertEquals("uaa", captor.getValue().getOrigin());
Assertions.assertNotNull(captor.getValue());
Assertions.assertEquals(loginHintOrigin, captor.getValue().getOrigin());
}

@Test
Expand Down Expand Up @@ -722,24 +734,30 @@ void testOIDCPasswordGrant_LoginHintOidcOverridesDefaultUaa() {
verify(identityProviderProvisioning, times(0)).retrieveActive(any());
}

@Test
void testOIDCPasswordGrant_LoginHintUaaOverridesDefaultOidc() {
@ParameterizedTest
@ValueSource(strings = { OriginKeys.UAA, OriginKeys.LDAP })
void testOIDCPasswordGrant_LoginHintUaaOrLdapOverridesDefaultOidc(final String loginHintOrigin) {
UaaLoginHint loginHint = mock(UaaLoginHint.class);
when(loginHint.getOrigin()).thenReturn("uaa");
when(loginHint.getOrigin()).thenReturn(loginHintOrigin);
Authentication auth = mock(Authentication.class);
when(zoneAwareAuthzAuthenticationManager.extractLoginHint(auth)).thenReturn(null);
when(zoneAwareAuthzAuthenticationManager.extractLoginHint(auth)).thenReturn(loginHint);
Map<String, Object> additionalInformation = new HashMap<>();
additionalInformation.put(ClientConstants.ALLOWED_PROVIDERS, Collections.singletonList("uaa"));
additionalInformation.put(ClientConstants.ALLOWED_PROVIDERS, Collections.singletonList(loginHintOrigin));
when(uaaClient.getAdditionalInformation()).thenReturn(additionalInformation);
IdentityZoneHolder.get().getConfig().setDefaultIdentityProvider("oidcprovider");

instance.authenticate(auth);

// should read only "uaa" or "ldap" IdP during lookup of possible providers
final String idzId = IdentityZoneHolder.get().getId();
verify(identityProviderProvisioning, times(0)).retrieveActive(idzId);
verify(identityProviderProvisioning, times(1)).retrieveByOrigin(loginHintOrigin, idzId);

verify(zoneAwareAuthzAuthenticationManager, times(1)).authenticate(auth);
ArgumentCaptor<UaaLoginHint> captor = ArgumentCaptor.forClass(UaaLoginHint.class);
verify(zoneAwareAuthzAuthenticationManager, times(1)).setLoginHint(eq(auth), captor.capture());
assertNotNull(captor.getValue());
assertEquals("uaa", captor.getValue().getOrigin());
Assertions.assertNotNull(captor.getValue());
Assertions.assertEquals(loginHintOrigin, captor.getValue().getOrigin());
}

@Test
Expand Down
Loading