keycloak-aplcache

Details

diff --git a/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java b/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java
index b178df9..b69cea8 100755
--- a/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java
+++ b/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java
@@ -67,12 +67,6 @@ public class OAuthFlows {
     }
 
     public Response redirectAccessCode(AccessCodeEntry accessCode, String state, String redirect) {
-        UserModel client = realm.getUser(accessCode.getClient().getLoginName());
-        Set<String> redirectUris = client.getRedirectUris();
-        if (!redirectUris.isEmpty() && !redirectUris.contains(redirect)) {
-            return forwardToSecurityFailure("Invalid redirect_uri " + redirect);
-        }
-
         String code = accessCode.getCode();
         UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam("code", code);
         log.debug("redirectAccessCode: state: {0}", state);
@@ -86,11 +80,6 @@ public class OAuthFlows {
     }
 
     public Response redirectError(UserModel client, String error, String state, String redirect) {
-        Set<String> redirectUris = client.getRedirectUris();
-        if (!redirectUris.isEmpty() && !redirectUris.contains(redirect)) {
-            return forwardToSecurityFailure("Invalid redirect_uri " + redirect);
-        }
-
         UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam("error", error);
         if (state != null) {
             redirectUri.queryParam("state", state);
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 2bc755e..b40e10e 100755
--- a/services/src/main/java/org/keycloak/services/resources/TokenService.java
+++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java
@@ -183,7 +183,7 @@ public class TokenService {
     @POST
     @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
     public Response processLogin(@QueryParam("client_id") final String clientId, @QueryParam("scope") final String scopeParam,
-            @QueryParam("state") final String state, @QueryParam("redirect_uri") final String redirect,
+            @QueryParam("state") final String state, @QueryParam("redirect_uri") String redirect,
             final MultivaluedMap<String, String> formData) {
         logger.debug("TokenService.processLogin");
         OAuthFlows oauth = Flows.oauth(realm, request, uriInfo, authManager, tokenManager);
@@ -199,6 +199,11 @@ public class TokenService {
             return oauth.forwardToSecurityFailure("Login requester not enabled.");
         }
 
+        redirect = verifyRedirectUri(redirect, client);
+        if (redirect == null) {
+            return oauth.forwardToSecurityFailure("Invalid redirect_uri.");
+        }
+
         if (formData.containsKey("cancel")) {
             return oauth.redirectError(client, "access_denied", state, redirect);
         }
@@ -290,6 +295,11 @@ public class TokenService {
             return oauth.forwardToSecurityFailure("Login requester not enabled.");
         }
 
+        redirect = verifyRedirectUri(redirect, client);
+        if (redirect == null) {
+            return oauth.forwardToSecurityFailure("Invalid redirect_uri.");
+        }
+
         if (!realm.isRegistrationAllowed()) {
             logger.warn("Registration not allowed");
             return oauth.forwardToSecurityFailure("Registration not allowed");
@@ -472,7 +482,7 @@ public class TokenService {
     @Path("login")
     @GET
     public Response loginPage(final @QueryParam("response_type") String responseType,
-            final @QueryParam("redirect_uri") String redirect, final @QueryParam("client_id") String clientId,
+            @QueryParam("redirect_uri") String redirect, final @QueryParam("client_id") String clientId,
             final @QueryParam("scope") String scopeParam, final @QueryParam("state") String state, final @QueryParam("prompt") String prompt) {
         logger.info("TokenService.loginPage");
         OAuthFlows oauth = Flows.oauth(realm, request, uriInfo, authManager, tokenManager);
@@ -497,6 +507,11 @@ public class TokenService {
             session.close();
             return null;
         }
+        redirect = verifyRedirectUri(redirect, client);
+        if (redirect == null) {
+            return oauth.forwardToSecurityFailure("Invalid redirect_uri.");
+        }
+
         logger.info("Checking roles...");
         RoleModel resourceRole = realm.getRole(Constants.APPLICATION_ROLE);
         RoleModel identityRequestRole = realm.getRole(Constants.IDENTITY_REQUESTER_ROLE);
@@ -525,7 +540,7 @@ public class TokenService {
     @Path("registrations")
     @GET
     public Response registerPage(final @QueryParam("response_type") String responseType,
-            final @QueryParam("redirect_uri") String redirect, final @QueryParam("client_id") String clientId,
+            @QueryParam("redirect_uri") String redirect, final @QueryParam("client_id") String clientId,
             final @QueryParam("scope") String scopeParam, final @QueryParam("state") String state) {
         logger.info("**********registerPage()");
         OAuthFlows oauth = Flows.oauth(realm, request, uriInfo, authManager, tokenManager);
@@ -545,6 +560,11 @@ public class TokenService {
             return oauth.forwardToSecurityFailure("Login requester not enabled.");
         }
 
+        redirect = verifyRedirectUri(redirect, client);
+        if (redirect == null) {
+            return oauth.forwardToSecurityFailure("Invalid redirect_uri.");
+        }
+
         if (!realm.isRegistrationAllowed()) {
             logger.warn("Registration not allowed");
             return oauth.forwardToSecurityFailure("Registration not allowed");
@@ -613,4 +633,15 @@ public class TokenService {
         return location.build();
     }
 
+    protected String verifyRedirectUri(String redirectUri, UserModel client) {
+        if (redirectUri == null) {
+            return client.getRedirectUris().size() == 1 ? client.getRedirectUris().iterator().next() : null;
+        } else if (client.getRedirectUris().isEmpty()) {
+            return redirectUri;
+        } else {
+            String r = redirectUri.indexOf('?') != -1 ? redirectUri.substring(0, redirectUri.indexOf('?')) : redirectUri;
+            return client.getRedirectUris().contains(r) ? 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 017aca5..f358882 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
@@ -98,31 +98,6 @@ public class AuthorizationCodeTest {
     }
 
     @Test
-    public void authorizationRequestInvalidRedirectUri() throws IOException {
-        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")) {
-                        UserModel client = app.getApplicationUser();
-                        client.addRedirectUri(oauth.getRedirectUri());
-                    }
-                }
-            }
-        });
-
-        oauth.redirectUri("http://invalid");
-        oauth.state("mystate");
-
-        AuthorizationCodeResponse response = oauth.doLogin("test-user@localhost", "password");
-
-        Assert.assertFalse(response.isRedirected());
-
-        Assert.assertTrue(errorPage.isCurrent());
-        Assert.assertEquals("Invalid redirect_uri http://invalid", errorPage.getError());
-    }
-
-    @Test
     public void authorizationRequestNoState() throws IOException {
         AuthorizationCodeResponse response = oauth.doLogin("test-user@localhost", "password");
 
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
new file mode 100755
index 0000000..5762c9d
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java
@@ -0,0 +1,174 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.testsuite.oauth;
+
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.representations.SkeletonKeyToken;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.testsuite.OAuthClient;
+import org.keycloak.testsuite.pages.ErrorPage;
+import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.pages.OAuthGrantPage;
+import org.keycloak.testsuite.rule.KeycloakRule;
+import org.keycloak.testsuite.rule.WebResource;
+import org.keycloak.testsuite.rule.WebRule;
+import org.openqa.selenium.WebDriver;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:vrockai@redhat.com">Viliam Rockai</a>
+ */
+public class OAuthRedirectUriTest {
+
+    @ClassRule
+    public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
+        @Override
+        public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+            ApplicationModel app = appRealm.getApplicationNameMap().get("test-app");
+            app.getApplicationUser().addRedirectUri("http://localhost:8081/app");
+        }
+    });
+
+    @Rule
+    public WebRule webRule = new WebRule(this);
+
+    @WebResource
+    protected WebDriver driver;
+
+    @WebResource
+    protected OAuthClient oauth;
+
+    @WebResource
+    protected LoginPage loginPage;
+
+    @WebResource
+    protected ErrorPage errorPage;
+
+    @Test
+    public void testNoParam() throws IOException {
+        oauth.redirectUri(null);
+        OAuthClient.AuthorizationCodeResponse response = oauth.doLogin("test-user@localhost", "password");
+
+        Assert.assertNotNull(response.getCode());
+        Assert.assertEquals(oauth.getCurrentRequest(), "http://localhost:8081/app");
+    }
+
+    @Test
+    public void testNoParamMultipleValidUris() throws IOException {
+        keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                appRealm.getApplicationNameMap().get("test-app").getApplicationUser().addRedirectUri("http://localhost:8081/app2");
+            }
+        });
+
+        try {
+            oauth.redirectUri(null);
+            oauth.openLoginForm();
+
+            Assert.assertTrue(errorPage.isCurrent());
+            Assert.assertEquals("Invalid redirect_uri.", errorPage.getError());
+        } finally {
+            keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+                @Override
+                public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                    appRealm.getApplicationNameMap().get("test-app").getApplicationUser().removeRedirectUri("http://localhost:8081/app2");
+                }
+            });
+        }
+    }
+
+    @Test
+    public void testNoParamNoValidUris() throws IOException {
+        keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                appRealm.getApplicationNameMap().get("test-app").getApplicationUser().removeRedirectUri("http://localhost:8081/app");
+            }
+        });
+
+        try {
+            oauth.redirectUri(null);
+            oauth.openLoginForm();
+
+            Assert.assertTrue(errorPage.isCurrent());
+            Assert.assertEquals("Invalid redirect_uri.", errorPage.getError());
+        } finally {
+            keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+                @Override
+                public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                    appRealm.getApplicationNameMap().get("test-app").getApplicationUser().addRedirectUri("http://localhost:8081/app");
+                }
+            });
+        }
+    }
+
+    @Test
+    public void testNoValidUris() throws IOException {
+        keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                appRealm.getApplicationNameMap().get("test-app").getApplicationUser().removeRedirectUri("http://localhost:8081/app");
+            }
+        });
+
+        try {
+            OAuthClient.AuthorizationCodeResponse response = oauth.doLogin("test-user@localhost", "password");
+
+            Assert.assertNotNull(response.getCode());
+            Assert.assertEquals(oauth.getCurrentRequest(), "http://localhost:8081/app/auth");
+        } finally {
+            keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+                @Override
+                public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                    appRealm.getApplicationNameMap().get("test-app").getApplicationUser().addRedirectUri("http://localhost:8081/app");
+                }
+            });
+        }
+    }
+
+    @Test
+    public void testInvalid() throws IOException {
+        oauth.redirectUri("http://localhost:8081/app2");
+        oauth.openLoginForm();
+
+        Assert.assertTrue(errorPage.isCurrent());
+        Assert.assertEquals("Invalid redirect_uri.", errorPage.getError());
+    }
+
+    @Test
+    public void testWithParams() throws IOException {
+        oauth.redirectUri("http://localhost:8081/app?key=value");
+        OAuthClient.AuthorizationCodeResponse response = oauth.doLogin("test-user@localhost", "password");
+
+        Assert.assertNotNull(response.getCode());
+        Assert.assertTrue(driver.getCurrentUrl().startsWith("http://localhost:8081/app?key=value&code="));
+    }
+
+}