diff --git a/server-spi/src/main/java/org/keycloak/models/KeycloakUriInfo.java b/server-spi/src/main/java/org/keycloak/models/KeycloakUriInfo.java index 2643cc0c11..c1d9156d1e 100644 --- a/server-spi/src/main/java/org/keycloak/models/KeycloakUriInfo.java +++ b/server-spi/src/main/java/org/keycloak/models/KeycloakUriInfo.java @@ -31,6 +31,7 @@ public class KeycloakUriInfo implements UriInfo { private final UriInfo delegate; private final String hostname; + private final String scheme; private final int port; private URI absolutePath; @@ -41,8 +42,10 @@ public class KeycloakUriInfo implements UriInfo { this.delegate = delegate; HostnameProvider hostnameProvider = session.getProvider(HostnameProvider.class); + this.scheme = hostnameProvider.getScheme(delegate); this.hostname = hostnameProvider.getHostname(delegate); this.port = hostnameProvider.getPort(delegate); + } public UriInfo getDelegate() { @@ -52,7 +55,7 @@ public class KeycloakUriInfo implements UriInfo { @Override public URI getRequestUri() { if (requestURI == null) { - requestURI = delegate.getRequestUriBuilder().host(hostname).port(port).build(); + requestURI = delegate.getRequestUriBuilder().scheme(scheme).host(hostname).port(port).build(); } return requestURI; } @@ -65,7 +68,7 @@ public class KeycloakUriInfo implements UriInfo { @Override public URI getAbsolutePath() { if (absolutePath == null) { - absolutePath = delegate.getAbsolutePathBuilder().host(hostname).port(port).build(); + absolutePath = delegate.getAbsolutePathBuilder().scheme(scheme).host(hostname).port(port).build(); } return absolutePath; } @@ -78,7 +81,7 @@ public class KeycloakUriInfo implements UriInfo { @Override public URI getBaseUri() { if (baseURI == null) { - baseURI = delegate.getBaseUriBuilder().host(hostname).port(port).build(); + baseURI = delegate.getBaseUriBuilder().scheme(scheme).host(hostname).port(port).build(); } return baseURI; } diff --git a/server-spi/src/main/java/org/keycloak/urls/HostnameProvider.java b/server-spi/src/main/java/org/keycloak/urls/HostnameProvider.java index 0d33b93ab3..35366c5304 100644 --- a/server-spi/src/main/java/org/keycloak/urls/HostnameProvider.java +++ b/server-spi/src/main/java/org/keycloak/urls/HostnameProvider.java @@ -23,6 +23,8 @@ import javax.ws.rs.core.UriInfo; public interface HostnameProvider extends Provider { + String getScheme(UriInfo originalUriInfo); + /** * Return the hostname. Http headers, realm details, etc. can be retrieved from the KeycloakSession. Do NOT use * {@link KeycloakContext#getUri()} as it will in turn call the HostnameProvider resulting in an infinite loop! diff --git a/services/src/main/java/org/keycloak/url/FixedHostnameProvider.java b/services/src/main/java/org/keycloak/url/FixedHostnameProvider.java index b6b5f9ccd6..0e31578ced 100644 --- a/services/src/main/java/org/keycloak/url/FixedHostnameProvider.java +++ b/services/src/main/java/org/keycloak/url/FixedHostnameProvider.java @@ -10,16 +10,23 @@ public class FixedHostnameProvider implements HostnameProvider { private final KeycloakSession session; private final String globalHostname; + private final String scheme; private final int httpPort; private final int httpsPort; - public FixedHostnameProvider(KeycloakSession session, String globalHostname, int httpPort, int httpsPort) { + public FixedHostnameProvider(KeycloakSession session, String scheme, String globalHostname, int httpPort, int httpsPort) { this.session = session; + this.scheme = scheme; this.globalHostname = globalHostname; this.httpPort = httpPort; this.httpsPort = httpsPort; } + @Override + public String getScheme(UriInfo originalUriInfo) { + return scheme != null ? scheme : originalUriInfo.getRequestUri().getScheme(); + } + @Override public String getHostname(UriInfo originalUriInfo) { RealmModel realm = session.getContext().getRealm(); diff --git a/services/src/main/java/org/keycloak/url/FixedHostnameProviderFactory.java b/services/src/main/java/org/keycloak/url/FixedHostnameProviderFactory.java index 1899cdadad..25a030ee38 100644 --- a/services/src/main/java/org/keycloak/url/FixedHostnameProviderFactory.java +++ b/services/src/main/java/org/keycloak/url/FixedHostnameProviderFactory.java @@ -10,10 +10,11 @@ public class FixedHostnameProviderFactory implements HostnameProviderFactory { private String hostname; private int httpPort; private int httpsPort; + private String scheme; @Override public HostnameProvider create(KeycloakSession session) { - return new FixedHostnameProvider(session, hostname, httpPort, httpsPort); + return new FixedHostnameProvider(session, scheme, hostname, httpPort, httpsPort); } @Override @@ -25,6 +26,10 @@ public class FixedHostnameProviderFactory implements HostnameProviderFactory { this.httpPort = config.getInt("httpPort", -1); this.httpsPort = config.getInt("httpsPort", -1); + this.scheme = config.get("scheme"); + if (scheme != null && scheme.trim().isEmpty()) { + scheme = null; + } } @Override diff --git a/services/src/main/java/org/keycloak/url/RequestHostnameProvider.java b/services/src/main/java/org/keycloak/url/RequestHostnameProvider.java index f5a6f00c2f..e561efbc29 100644 --- a/services/src/main/java/org/keycloak/url/RequestHostnameProvider.java +++ b/services/src/main/java/org/keycloak/url/RequestHostnameProvider.java @@ -6,6 +6,11 @@ import javax.ws.rs.core.UriInfo; public class RequestHostnameProvider implements HostnameProvider { + @Override + public String getScheme(UriInfo originalUriInfo) { + return originalUriInfo.getRequestUri().getScheme(); + } + @Override public String getHostname(UriInfo originalUriInfo) { return originalUriInfo.getBaseUri().getHost(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/url/FixedHostnameTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/url/FixedHostnameTest.java index 518e795b19..35381af3b3 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/url/FixedHostnameTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/url/FixedHostnameTest.java @@ -19,6 +19,7 @@ import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.testsuite.AbstractKeycloakTest; import org.keycloak.testsuite.arquillian.AuthServerTestEnricher; +import org.keycloak.testsuite.util.ClientBuilder; import org.keycloak.testsuite.util.OAuthClient; import org.wildfly.extras.creaper.core.online.OnlineManagementClient; import org.wildfly.extras.creaper.core.online.operations.admin.Administration; @@ -46,37 +47,62 @@ public class FixedHostnameTest extends AbstractKeycloakTest { customHostname.setRealm("hostname"); customHostname.setAttributes(new HashMap<>()); customHostname.getAttributes().put("hostname", "custom-domain.127.0.0.1.nip.io"); + testRealms.add(customHostname); } @Test public void fixedHostname() throws Exception { + oauth.clientId("direct-grant"); + try { - assertWellKnown("test", "localhost"); + assertWellKnown("test", "http","localhost"); - configureFixedHostname(); + configureFixedHostname(null); - assertWellKnown("test", "keycloak.127.0.0.1.nip.io"); - assertWellKnown("hostname", "custom-domain.127.0.0.1.nip.io"); + assertWellKnown("test", "http","keycloak.127.0.0.1.nip.io"); + assertWellKnown("hostname", "http","custom-domain.127.0.0.1.nip.io"); - assertTokenIssuer("test", "keycloak.127.0.0.1.nip.io"); - assertTokenIssuer("hostname", "custom-domain.127.0.0.1.nip.io"); + assertTokenIssuer("test", "http","keycloak.127.0.0.1.nip.io"); + assertTokenIssuer("hostname", "http","custom-domain.127.0.0.1.nip.io"); - assertInitialAccessTokenFromMasterRealm("test", "keycloak.127.0.0.1.nip.io"); - assertInitialAccessTokenFromMasterRealm("hostname", "custom-domain.127.0.0.1.nip.io"); + assertInitialAccessTokenFromMasterRealm("test","http","keycloak.127.0.0.1.nip.io"); + assertInitialAccessTokenFromMasterRealm("hostname", "http","custom-domain.127.0.0.1.nip.io"); } finally { clearFixedHostname(); } } - private void assertInitialAccessTokenFromMasterRealm(String realm, String expectedHostname) throws JWSInputException, ClientRegistrationException { + @Test + public void fixedHostnameAndScheme() throws Exception { + oauth.clientId("direct-grant"); + + try { + assertWellKnown("test", "http","localhost"); + + configureFixedHostname("https"); + + assertWellKnown("test", "https","keycloak.127.0.0.1.nip.io"); + assertWellKnown("hostname", "https","custom-domain.127.0.0.1.nip.io"); + + assertTokenIssuer("test", "https","keycloak.127.0.0.1.nip.io"); + assertTokenIssuer("hostname", "https","custom-domain.127.0.0.1.nip.io"); + + assertInitialAccessTokenFromMasterRealm("test", "https", "keycloak.127.0.0.1.nip.io"); + assertInitialAccessTokenFromMasterRealm("hostname", "https", "custom-domain.127.0.0.1.nip.io"); + } finally { + clearFixedHostname(); + } + } + + private void assertInitialAccessTokenFromMasterRealm(String realm, String expectedScheme, String expectedHostname) throws JWSInputException, ClientRegistrationException { ClientInitialAccessCreatePresentation rep = new ClientInitialAccessCreatePresentation(); rep.setCount(1); rep.setExpiration(10000); ClientInitialAccessPresentation initialAccess = adminClient.realm(realm).clientInitialAccess().create(rep); JsonWebToken token = new JWSInput(initialAccess.getToken()).readJsonContent(JsonWebToken.class); - assertEquals("http://" + expectedHostname + ":8180/auth/realms/" + realm, token.getIssuer()); + assertEquals(expectedScheme + "://" + expectedHostname + ":8180/auth/realms/" + realm, token.getIssuer()); ClientRegistration clientReg = ClientRegistration.create().url(suiteContext.getAuthServerInfo().getContextRoot() + "/auth", realm).build(); clientReg.auth(Auth.token(initialAccess.getToken())); @@ -87,36 +113,34 @@ public class FixedHostnameTest extends AbstractKeycloakTest { String registrationAccessToken = response.getRegistrationAccessToken(); JsonWebToken registrationToken = new JWSInput(registrationAccessToken).readJsonContent(JsonWebToken.class); - assertEquals("http://" + expectedHostname + ":8180/auth/realms/" + realm, registrationToken.getIssuer()); + assertEquals(expectedScheme + "://" + expectedHostname + ":8180/auth/realms/" + realm, registrationToken.getIssuer()); } - private void assertTokenIssuer(String realm, String expectedHostname) throws JWSInputException, IOException { - oauth.baseUrl("http://" + expectedHostname + ":8180/auth"); + private void assertTokenIssuer(String realm, String expectedScheme, String expectedHostname) throws Exception { + oauth.realm(realm); - OAuthClient.AuthorizationEndpointResponse response = oauth.realm(realm).doLogin("test-user@localhost", "password"); - - OAuthClient.AccessTokenResponse tokenResponse = oauth.baseUrl(OAuthClient.AUTH_SERVER_ROOT).doAccessTokenRequest(response.getCode(), "password"); + OAuthClient.AccessTokenResponse tokenResponse = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password"); AccessToken token = new JWSInput(tokenResponse.getAccessToken()).readJsonContent(AccessToken.class); - assertEquals("http://" + expectedHostname + ":8180/auth/realms/" + realm, token.getIssuer()); + assertEquals(expectedScheme + "://" + expectedHostname + ":8180/auth/realms/" + realm, token.getIssuer()); String introspection = oauth.introspectAccessTokenWithClientCredential(oauth.getClientId(), "password", tokenResponse.getAccessToken()); ObjectMapper objectMapper = new ObjectMapper(); JsonNode introspectionNode = objectMapper.readTree(introspection); assertTrue(introspectionNode.get("active").asBoolean()); - assertEquals("http://" + expectedHostname + ":8180/auth/realms/" + realm, introspectionNode.get("iss").asText()); + assertEquals(expectedScheme + "://" + expectedHostname + ":8180/auth/realms/" + realm, introspectionNode.get("iss").asText()); } - private void assertWellKnown(String realm, String expectedHostname) { + private void assertWellKnown(String realm, String expectedScheme, String expectedHostname) { OIDCConfigurationRepresentation config = oauth.doWellKnownRequest(realm); - assertEquals("http://" + expectedHostname + ":8180/auth/realms/" + realm + "/protocol/openid-connect/token", config.getTokenEndpoint()); + assertEquals(expectedScheme + "://" + expectedHostname + ":8180/auth/realms/" + realm + "/protocol/openid-connect/token", config.getTokenEndpoint()); } - private void configureFixedHostname() throws Exception { + private void configureFixedHostname(String scheme) throws Exception { if (suiteContext.getAuthServerInfo().isUndertow()) { - configureUndertow("fixed", "keycloak.127.0.0.1.nip.io"); + configureUndertow("fixed", "keycloak.127.0.0.1.nip.io", scheme); } else if (suiteContext.getAuthServerInfo().isJBossBased()) { - configureWildFly("fixed", "keycloak.127.0.0.1.nip.io"); + configureWildFly("fixed", "keycloak.127.0.0.1.nip.io", scheme); } else { throw new RuntimeException("Don't know how to config"); } @@ -127,9 +151,9 @@ public class FixedHostnameTest extends AbstractKeycloakTest { private void clearFixedHostname() throws Exception { if (suiteContext.getAuthServerInfo().isUndertow()) { - configureUndertow("request", "localhost"); + configureUndertow("request", "localhost", null); } else if (suiteContext.getAuthServerInfo().isJBossBased()) { - configureWildFly("request", "localhost"); + configureWildFly("request", "localhost", null); } else { throw new RuntimeException("Don't know how to config"); } @@ -137,21 +161,31 @@ public class FixedHostnameTest extends AbstractKeycloakTest { reconnectAdminClient(); } - private void configureUndertow(String provider, String hostname) { + private void configureUndertow(String provider, String hostname, String scheme) { controller.stop(suiteContext.getAuthServerInfo().getQualifier()); System.setProperty("keycloak.hostname.provider", provider); System.setProperty("keycloak.hostname.fixed.hostname", hostname); + if (scheme != null) { + System.setProperty("keycloak.hostname.fixed.scheme", scheme); + } else { + System.getProperties().remove("keycloak.hostname.fixed.scheme"); + } controller.start(suiteContext.getAuthServerInfo().getQualifier()); } - private void configureWildFly(String provider, String hostname) throws Exception { + private void configureWildFly(String provider, String hostname, String scheme) throws Exception { OnlineManagementClient client = AuthServerTestEnricher.getManagementClient(); Administration administration = new Administration(client); client.execute("/subsystem=keycloak-server/spi=hostname:write-attribute(name=default-provider, value=" + provider + ")"); client.execute("/subsystem=keycloak-server/spi=hostname/provider=fixed:write-attribute(name=properties.hostname,value=" + hostname + ")"); + if (scheme != null) { + client.execute("/subsystem=keycloak-server/spi=hostname/provider=fixed:write-attribute(name=properties.scheme,value=" + scheme + ")"); + } else { + client.execute("/subsystem=keycloak-server/spi=hostname/provider=fixed:map-remove(name=properties,key=scheme)"); + } administration.reloadIfRequired(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json index e1e7dcaa4e..3fd578e340 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json @@ -6,7 +6,8 @@ "fixed": { "hostname": "${keycloak.hostname.fixed.hostname:localhost}", "httpPort": "${keycloak.hostname.fixed.httpPort:-1}", - "httpsPort": "${keycloak.hostname.fixed.httpPorts:-1}" + "httpsPort": "${keycloak.hostname.fixed.httpPorts:-1}", + "scheme": "${keycloak.hostname.fixed.scheme:}" } },