package org.keycloak.protocol.oidc.utils; import org.keycloak.connections.httpclient.HttpClientProvider; import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.ProtocolMapperModel; import org.keycloak.protocol.ProtocolMapperConfigException; import org.keycloak.protocol.oidc.mappers.PairwiseSubMapperHelper; import org.keycloak.util.JsonSerialization; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; /** * @author <a href="mailto:martin.hardselius@gmail.com">Martin Hardselius</a> */ public class PairwiseSubMapperValidator { public static final String PAIRWISE_MALFORMED_CLIENT_REDIRECT_URI = "pairwiseMalformedClientRedirectURI"; public static final String PAIRWISE_CLIENT_REDIRECT_URIS_MISSING_HOST = "pairwiseClientRedirectURIsMissingHost"; public static final String PAIRWISE_CLIENT_REDIRECT_URIS_MULTIPLE_HOSTS = "pairwiseClientRedirectURIsMultipleHosts"; public static final String PAIRWISE_MALFORMED_SECTOR_IDENTIFIER_URI = "pairwiseMalformedSectorIdentifierURI"; public static final String PAIRWISE_FAILED_TO_GET_REDIRECT_URIS = "pairwiseFailedToGetRedirectURIs"; public static final String PAIRWISE_REDIRECT_URIS_MISMATCH = "pairwiseRedirectURIsMismatch"; public static void validate(KeycloakSession session, ClientModel client, ProtocolMapperModel mapperModel) throws ProtocolMapperConfigException { String sectorIdentifierUri = PairwiseSubMapperHelper.getSectorIdentifierUri(mapperModel); String rootUrl = client.getRootUrl(); Set<String> redirectUris = client.getRedirectUris(); validate(session, rootUrl, redirectUris, sectorIdentifierUri); } public static void validate(KeycloakSession session, String rootUrl, Set<String> redirectUris, String sectorIdentifierUri) throws ProtocolMapperConfigException { if (sectorIdentifierUri == null || sectorIdentifierUri.isEmpty()) { validateClientRedirectUris(rootUrl, redirectUris); return; } validateSectorIdentifierUri(sectorIdentifierUri); validateSectorIdentifierUri(session, rootUrl, redirectUris, sectorIdentifierUri); } private static void validateClientRedirectUris(String rootUrl, Set<String> redirectUris) throws ProtocolMapperConfigException { Set<String> hosts = new HashSet<>(); for (String redirectUri : PairwiseSubMapperUtils.resolveValidRedirectUris(rootUrl, redirectUris)) { try { URI uri = new URI(redirectUri); hosts.add(uri.getHost()); } catch (URISyntaxException e) { throw new ProtocolMapperConfigException("Client contained an invalid redirect URI.", PAIRWISE_MALFORMED_CLIENT_REDIRECT_URI, e); } } if (hosts.isEmpty()) { throw new ProtocolMapperConfigException("Client redirect URIs must contain a valid host component.", PAIRWISE_CLIENT_REDIRECT_URIS_MISSING_HOST); } if (hosts.size() > 1) { throw new ProtocolMapperConfigException("Without a configured Sector Identifier URI, client redirect URIs must not contain multiple host components.", PAIRWISE_CLIENT_REDIRECT_URIS_MULTIPLE_HOSTS); } } private static void validateSectorIdentifierUri(String sectorIdentifierUri) throws ProtocolMapperConfigException { URI uri; try { uri = new URI(sectorIdentifierUri); } catch (URISyntaxException e) { throw new ProtocolMapperConfigException("Invalid Sector Identifier URI.", PAIRWISE_MALFORMED_SECTOR_IDENTIFIER_URI, e); } if (uri.getScheme() == null || uri.getHost() == null) { throw new ProtocolMapperConfigException("Invalid Sector Identifier URI.", PAIRWISE_MALFORMED_SECTOR_IDENTIFIER_URI); } } private static void validateSectorIdentifierUri(KeycloakSession session, String rootUrl, Set<String> redirectUris, String sectorIdentifierUri) throws ProtocolMapperConfigException { Set<String> sectorRedirects = getSectorRedirects(session, sectorIdentifierUri); if (!PairwiseSubMapperUtils.matchesRedirects(rootUrl, redirectUris, sectorRedirects)) { throw new ProtocolMapperConfigException("Client redirect URIs does not match redirect URIs fetched from the Sector Identifier URI.", PAIRWISE_REDIRECT_URIS_MISMATCH); } } private static Set<String> getSectorRedirects(KeycloakSession session, String sectorIdentifierUri) throws ProtocolMapperConfigException { InputStream is = null; try { is = session.getProvider(HttpClientProvider.class).get(sectorIdentifierUri); List<String> sectorRedirects = JsonSerialization.readValue(is, TypedList.class); return new HashSet<>(sectorRedirects); } catch (IOException e) { throw new ProtocolMapperConfigException("Failed to get redirect URIs from the Sector Identifier URI.", PAIRWISE_FAILED_TO_GET_REDIRECT_URIS, e); } finally { if (is != null) { try { is.close(); } catch (IOException ignored) { } } } } public static class TypedList extends ArrayList<String> {} }