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 0630397..bb9823e 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 0cae7c2..1cbdf5f 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 4e1f043..932c777 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 55f66fc..0152868 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");
}
});
@@ -151,6 +161,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");
oauth.openLoginForm();
@@ -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());
+ }
+ }
+
}