diff --git a/model/api/src/main/java/org/keycloak/models/Constants.java b/model/api/src/main/java/org/keycloak/models/Constants.java index 0630397295..bb9823e07f 100755 --- a/model/api/src/main/java/org/keycloak/models/Constants.java +++ b/model/api/src/main/java/org/keycloak/models/Constants.java @@ -13,4 +13,5 @@ public interface Constants { String ACCOUNT_MANAGEMENT_APP = "account"; String INSTALLED_APP_URN = "urn:ietf:wg:oauth:2.0:oob"; + String INSTALLED_APP_URL = "http://localhost"; } diff --git a/services/src/main/java/org/keycloak/services/resources/TokenService.java b/services/src/main/java/org/keycloak/services/resources/TokenService.java index 0cae7c2caa..1cbdf5f64d 100755 --- a/services/src/main/java/org/keycloak/services/resources/TokenService.java +++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java @@ -8,6 +8,7 @@ import org.keycloak.OAuthErrorException; import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.crypto.RSAProvider; import org.keycloak.models.ClientModel; +import org.keycloak.models.Constants; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakTransaction; import org.keycloak.models.RealmModel; @@ -619,7 +620,26 @@ public class TokenService { return redirectUri; } else { String r = redirectUri.indexOf('?') != -1 ? redirectUri.substring(0, redirectUri.indexOf('?')) : redirectUri; - return client.getRedirectUris().contains(r) ? redirectUri : null; + + boolean valid = client.getRedirectUris().contains(r); + + if (!valid && r.startsWith(Constants.INSTALLED_APP_URL) && r.indexOf(':', Constants.INSTALLED_APP_URL.length()) >= 0) { + int i = r.indexOf(':', Constants.INSTALLED_APP_URL.length()); + + StringBuilder sb = new StringBuilder(); + sb.append(r.substring(0, i)); + + i = r.indexOf('/', i); + if (i >= 0) { + sb.append(r.substring(i)); + } + + r = sb.toString(); + + valid = client.getRedirectUris().contains(r); + } + + return valid ? redirectUri : null; } } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java index 4e1f043304..932c7777de 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java @@ -96,11 +96,7 @@ public class AuthorizationCodeTest { keycloakRule.configure(new KeycloakRule.KeycloakSetup() { @Override public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { - for (ApplicationModel app : appRealm.getApplications()) { - if (app.getName().equals("test-app")) { - app.addRedirectUri(oauth.getRedirectUri()); - } - } + appRealm.getApplicationByName("test-app").addRedirectUri(oauth.getRedirectUri()); } }); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java index 55f66fc723..0152868d5f 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java @@ -26,6 +26,7 @@ import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.keycloak.models.ApplicationModel; +import org.keycloak.models.Constants; import org.keycloak.models.RealmModel; import org.keycloak.services.managers.RealmManager; import org.keycloak.testsuite.OAuthClient; @@ -49,6 +50,15 @@ public class OAuthRedirectUriTest { public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { ApplicationModel app = appRealm.getApplicationNameMap().get("test-app"); app.addRedirectUri("http://localhost:8081/app"); + + ApplicationModel installedApp = appRealm.addApplication("test-installed"); + installedApp.setEnabled(true); + installedApp.addRedirectUri(Constants.INSTALLED_APP_URN); + installedApp.addRedirectUri(Constants.INSTALLED_APP_URL); + + ApplicationModel installedApp2 = appRealm.addApplication("test-installed2"); + installedApp2.setEnabled(true); + installedApp2.addRedirectUri(Constants.INSTALLED_APP_URL + "/myapp"); } }); @@ -150,6 +160,15 @@ public class OAuthRedirectUriTest { } } + @Test + public void testValid() throws IOException { + oauth.redirectUri("http://localhost:8081/app"); + OAuthClient.AuthorizationCodeResponse response = oauth.doLogin("test-user@localhost", "password"); + + Assert.assertNotNull(response.getCode()); + Assert.assertTrue(driver.getCurrentUrl().startsWith("http://localhost:8081/app?code=")); + } + @Test public void testInvalid() throws IOException { oauth.redirectUri("http://localhost:8081/app2"); @@ -168,4 +187,40 @@ public class OAuthRedirectUriTest { Assert.assertTrue(driver.getCurrentUrl().startsWith("http://localhost:8081/app?key=value&code=")); } + + @Test + public void testLocalhost() throws IOException { + oauth.clientId("test-installed"); + + checkRedirectUri("urn:ietf:wg:oauth:2.0:oob", true); + checkRedirectUri("http://localhost", true); + + checkRedirectUri("http://localhost:8081", true); + + checkRedirectUri("http://localhosts", false); + checkRedirectUri("http://localhost/myapp", false); + checkRedirectUri("http://localhost:8081/myapp", false); + + oauth.clientId("test-installed2"); + + checkRedirectUri("http://localhost/myapp", true); + checkRedirectUri("http://localhost:8081/myapp", true); + + checkRedirectUri("http://localhosts/myapp", false); + checkRedirectUri("http://localhost", false); + checkRedirectUri("http://localhost/myapp2", false); + } + + private void checkRedirectUri(String redirectUri, boolean expectValid) { + oauth.redirectUri(redirectUri); + oauth.openLoginForm(); + + if (expectValid) { + Assert.assertTrue(loginPage.isCurrent()); + } else { + Assert.assertTrue(errorPage.isCurrent()); + Assert.assertEquals("Invalid redirect_uri.", errorPage.getError()); + } + } + }