keycloak-uncached

Added redirect uris to application

10/17/2013 11:00:46 AM

Details

diff --git a/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java
index ab12aae..7b7fa9d 100755
--- a/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java
@@ -20,6 +20,7 @@ public class ApplicationRepresentation {
     protected List<RoleRepresentation> roles;
     protected List<UserRoleMappingRepresentation> roleMappings;
     protected List<ScopeMappingRepresentation> scopeMappings;
+    protected List<String> redirectUris;
 
     public String getSelf() {
         return self;
@@ -146,4 +147,12 @@ public class ApplicationRepresentation {
     public void setUseRealmMappings(boolean useRealmMappings) {
         this.useRealmMappings = useRealmMappings;
     }
+
+    public List<String> getRedirectUris() {
+        return redirectUris;
+    }
+
+    public void setRedirectUris(List<String> redirectUris) {
+        this.redirectUris = redirectUris;
+    }
 }
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/error.ftl b/forms/src/main/resources/META-INF/resources/forms/theme/default/error.ftl
index 64ca460..659fce1 100755
--- a/forms/src/main/resources/META-INF/resources/forms/theme/default/error.ftl
+++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/error.ftl
@@ -12,8 +12,7 @@
     <#elseif section = "form">
 
         <p class="instruction">Something happened and we could not process your request.</p>
-        <p class="instruction second">${error.summary}</p>
-        <a href="saas-login.html" class="link-right">Go to the homepage »</a>
+        <p id="error-summary" class="instruction second">${error.summary}</p>
 
     <#elseif section = "info" >
 
diff --git a/model/api/src/main/java/org/keycloak/models/UserModel.java b/model/api/src/main/java/org/keycloak/models/UserModel.java
index 2131d3c..8598ae7 100755
--- a/model/api/src/main/java/org/keycloak/models/UserModel.java
+++ b/model/api/src/main/java/org/keycloak/models/UserModel.java
@@ -35,6 +35,14 @@ public interface UserModel {
 
     void removeRequiredAction(RequiredAction action);
 
+    Set<String> getRedirectUris();
+
+    void setRedirectUris(Set<String> redirectUris);
+
+    void addRedirectUri(String redirectUri);
+
+    void removeRedirectUri(String redirectUri);
+
     String getFirstName();
 
     void setFirstName(String firstName);
diff --git a/model/picketlink/src/main/java/org/keycloak/models/picketlink/UserAdapter.java b/model/picketlink/src/main/java/org/keycloak/models/picketlink/UserAdapter.java
index de303b4..98894ce 100755
--- a/model/picketlink/src/main/java/org/keycloak/models/picketlink/UserAdapter.java
+++ b/model/picketlink/src/main/java/org/keycloak/models/picketlink/UserAdapter.java
@@ -1,6 +1,6 @@
 package org.keycloak.models.picketlink;
 
-import java.util.Arrays;
+import java.io.Serializable;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -8,7 +8,6 @@ import java.util.Map;
 import java.util.Set;
 
 import org.keycloak.models.UserModel;
-import org.keycloak.models.utils.ArrayUtils;
 import org.picketlink.idm.IdentityManager;
 import org.picketlink.idm.model.Attribute;
 import org.picketlink.idm.model.sample.User;
@@ -22,6 +21,8 @@ public class UserAdapter implements UserModel {
     private static final String KEYCLOAK_TOTP_ATTR = "totpEnabled";
     private static final String REQUIRED_ACTIONS_ATTR = "requiredActions";
 
+    private static final String REDIRECT_URIS = "redirectUris";
+
     protected User user;
     protected IdentityManager idm;
 
@@ -110,7 +111,8 @@ public class UserAdapter implements UserModel {
     @Override
     public String getAttribute(String name) {
         Attribute<String> attribute = user.getAttribute(name);
-        if (attribute == null || attribute.getValue() == null) return null;
+        if (attribute == null || attribute.getValue() == null)
+            return null;
         return attribute.getValue().toString();
     }
 
@@ -118,73 +120,45 @@ public class UserAdapter implements UserModel {
     public Map<String, String> getAttributes() {
         Map<String, String> attributes = new HashMap<String, String>();
         for (Attribute<?> attribute : user.getAttributes()) {
-           if (attribute.getValue() != null) attributes.put(attribute.getName(), attribute.getValue().toString());
+            if (attribute.getValue() != null)
+                attributes.put(attribute.getName(), attribute.getValue().toString());
         }
         return attributes;
     }
 
-    private RequiredAction[] getRequiredActionsArray() {
-        Attribute<?> a = user.getAttribute(REQUIRED_ACTIONS_ATTR);
-        if (a == null) {
-            return null;
-        }
-
-        Object o = a.getValue();
-        if (o instanceof RequiredAction) {
-            return new RequiredAction[] { (RequiredAction) o };
-        } else {
-            return (RequiredAction[]) o;
-        }
-    }
-
     @Override
     public Set<RequiredAction> getRequiredActions() {
-        RequiredAction[] actions = getRequiredActionsArray();
-        if (actions == null) {
-            return Collections.emptySet();
-        } else {
-            Set<RequiredAction> s = new HashSet<RequiredAction>();
-            for (RequiredAction a : actions) {
-                s.add(a);
-            }
-            return Collections.unmodifiableSet(s);
-        }
+        return getAttributeSet(REQUIRED_ACTIONS_ATTR);
     }
 
     @Override
     public void addRequiredAction(RequiredAction action) {
-        RequiredAction[] actions = getRequiredActionsArray();
-        if (actions == null) {
-            actions = new RequiredAction[] { action };
-        } else {
-            if (Arrays.binarySearch(actions, action) < 0) {
-                actions = ArrayUtils.add(actions, action);
-            }
-        }
+        addToAttributeSet(REQUIRED_ACTIONS_ATTR, action);
+    }
 
-        Attribute<RequiredAction[]> a = new Attribute<RequiredAction[]>(REQUIRED_ACTIONS_ATTR, actions);
+    @Override
+    public void removeRequiredAction(RequiredAction action) {
+        removeFromAttributeSet(REQUIRED_ACTIONS_ATTR, action);
+    }
 
-        user.setAttribute(a);
-        idm.update(user);
+    @Override
+    public Set<String> getRedirectUris() {
+        return getAttributeSet(REDIRECT_URIS);
     }
 
     @Override
-    public void removeRequiredAction(RequiredAction action) {
-        RequiredAction[] actions = getRequiredActionsArray();
-        if (actions != null) {
-            if (Arrays.binarySearch(actions, action) >= 0) {
-                actions = ArrayUtils.remove(actions, action);
-
-                if (actions.length == 0) {
-                    user.removeAttribute(REQUIRED_ACTIONS_ATTR);
-                } else {
-                    Attribute<RequiredAction[]> a = new Attribute<RequiredAction[]>(REQUIRED_ACTIONS_ATTR, actions);
-                    user.setAttribute(a);
-                }
+    public void setRedirectUris(Set<String> redirectUris) {
+        setAttributeSet(REDIRECT_URIS, redirectUris);
+    }
 
-                idm.update(user);
-            }
-        }
+    @Override
+    public void addRedirectUri(String redirectUri) {
+        addToAttributeSet(REDIRECT_URIS, redirectUri);
+    }
+
+    @Override
+    public void removeRedirectUri(String redirectUri) {
+        removeFromAttributeSet(REDIRECT_URIS, redirectUri);
     }
 
     @Override
@@ -199,4 +173,57 @@ public class UserAdapter implements UserModel {
         idm.update(user);
     }
 
+    @SuppressWarnings("unchecked")
+    private <T extends Serializable> Set<T> getAttributeSet(String name) {
+        Attribute<Serializable> a = user.getAttribute(name);
+
+        Set<Serializable> s = new HashSet<Serializable>();
+
+        if (a != null) {
+            Serializable o = a.getValue();
+            if (o instanceof Serializable[]) {
+                for (Serializable t : (Serializable[]) o) {
+                    s.add(t);
+                }
+            } else {
+                s.add(o);
+            }
+        }
+
+        return (Set<T>) s;
+    }
+
+    private <T extends Serializable> void setAttributeSet(String name, Set<T> set) {
+        if (set.isEmpty()) {
+            user.removeAttribute(name);
+        } else {
+            user.setAttribute(new Attribute<Serializable[]>(name, set.toArray(new Serializable[set.size()])));
+        }
+        idm.update(user);
+    }
+
+    private <T extends Serializable> void addToAttributeSet(String name, T t) {
+        Set<Serializable> set = getAttributeSet(name);
+        if (set == null) {
+            set = new HashSet<Serializable>();
+        }
+
+        if (set.add(t)) {
+            setAttributeSet(name, set);
+            idm.update(user);
+        }
+    }
+
+    private <T extends Serializable> void removeFromAttributeSet(String name, T t) {
+        Set<Serializable> set = getAttributeSet(name);
+        if (set == null) {
+            return;
+        }
+
+        if (set.remove(t)) {
+            setAttributeSet(name, set);
+            idm.update(user);
+        }
+    }
+
 }
diff --git a/services/src/main/java/org/keycloak/services/managers/ApplicationManager.java b/services/src/main/java/org/keycloak/services/managers/ApplicationManager.java
index 7156f89..93c2959 100755
--- a/services/src/main/java/org/keycloak/services/managers/ApplicationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ApplicationManager.java
@@ -1,5 +1,10 @@
 package org.keycloak.services.managers;
 
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
 import org.keycloak.models.*;
 import org.keycloak.representations.idm.*;
 
@@ -32,6 +37,12 @@ public class ApplicationManager {
                 realm.updateCredential(resourceUser, credential);
             }
         }
+        if (resourceRep.getRedirectUris() != null) {
+            for (String redirectUri : resourceRep.getRedirectUris()) {
+                resourceUser.addRedirectUri(redirectUri);
+            }
+        }
+
         realm.grantRole(resourceUser, loginRole);
 
 
@@ -82,6 +93,10 @@ public class ApplicationManager {
         resource.setSurrogateAuthRequired(rep.isSurrogateAuthRequired());
         resource.updateApplication();
 
+        List<String> redirectUris = rep.getRedirectUris();
+        if (redirectUris != null) {
+            resource.getApplicationUser().setRedirectUris(new HashSet<String>(redirectUris));
+        }
     }
 
     public ApplicationRepresentation toRepresentation(ApplicationModel applicationModel) {
@@ -92,6 +107,12 @@ public class ApplicationManager {
         rep.setAdminUrl(applicationModel.getManagementUrl());
         rep.setSurrogateAuthRequired(applicationModel.isSurrogateAuthRequired());
         rep.setBaseUrl(applicationModel.getBaseUrl());
+
+        Set<String> redirectUris = applicationModel.getApplicationUser().getRedirectUris();
+        if (redirectUris != null) {
+            rep.setRedirectUris(new LinkedList<String>(redirectUris));
+        }
+
         return rep;
 
     }
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 5d01a39..a9bc79b 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
@@ -68,6 +68,11 @@ public class OAuthFlows {
     }
 
     public Response redirectAccessCode(AccessCodeEntry accessCode, String state, String redirect) {
+        Set<String> redirectUris = accessCode.getClient().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.info("redirectAccessCode: state: " + state);
diff --git a/services/src/test/java/org/keycloak/test/AdapterTest.java b/services/src/test/java/org/keycloak/test/AdapterTest.java
index 7342e5e..22c38b3 100755
--- a/services/src/test/java/org/keycloak/test/AdapterTest.java
+++ b/services/src/test/java/org/keycloak/test/AdapterTest.java
@@ -302,42 +302,4 @@ public class AdapterTest extends AbstractKeycloakTest {
         Assert.assertEquals("user", role.getName());
     }
 
-    @Test
-    public void testUserRequiredActions() throws Exception {
-        test1CreateRealm();
-
-        UserModel user = realmModel.addUser("bburke");
-
-        Assert.assertTrue(user.getRequiredActions().isEmpty());
-
-        user.addRequiredAction(UserModel.RequiredAction.CONFIGURE_TOTP);
-        user = realmModel.getUser("bburke");
-
-        Assert.assertEquals(1, user.getRequiredActions().size());
-        Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.CONFIGURE_TOTP));
-
-        user.addRequiredAction(UserModel.RequiredAction.CONFIGURE_TOTP);
-        user = realmModel.getUser("bburke");
-
-        Assert.assertEquals(1, user.getRequiredActions().size());
-        Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.CONFIGURE_TOTP));
-
-        user.addRequiredAction(UserModel.RequiredAction.VERIFY_EMAIL);
-        user = realmModel.getUser("bburke");
-
-        Assert.assertEquals(2, user.getRequiredActions().size());
-        Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.CONFIGURE_TOTP));
-        Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL));
-
-        user.removeRequiredAction(UserModel.RequiredAction.CONFIGURE_TOTP);
-        user = realmModel.getUser("bburke");
-
-        Assert.assertEquals(1, user.getRequiredActions().size());
-        Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL));
-
-        user.removeRequiredAction(UserModel.RequiredAction.VERIFY_EMAIL);
-        user = realmModel.getUser("bburke");
-
-        Assert.assertTrue(user.getRequiredActions().isEmpty());
-    }
 }
diff --git a/services/src/test/java/org/keycloak/test/ApplicationModelTest.java b/services/src/test/java/org/keycloak/test/ApplicationModelTest.java
new file mode 100644
index 0000000..4466079
--- /dev/null
+++ b/services/src/test/java/org/keycloak/test/ApplicationModelTest.java
@@ -0,0 +1,100 @@
+package org.keycloak.test;
+
+import java.util.Iterator;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.representations.idm.ApplicationRepresentation;
+import org.keycloak.services.managers.ApplicationManager;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.services.resources.KeycloakApplication;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ApplicationModelTest extends AbstractKeycloakServerTest {
+    private KeycloakSessionFactory factory;
+    private KeycloakSession identitySession;
+    private RealmManager manager;
+    private ApplicationModel application;
+    private RealmModel realm;
+    private ApplicationManager appManager;
+
+    @Before
+    public void before() throws Exception {
+        factory = KeycloakApplication.buildSessionFactory();
+        identitySession = factory.createSession();
+        identitySession.getTransaction().begin();
+        manager = new RealmManager(identitySession);
+
+        appManager = new ApplicationManager(manager);
+
+        realm = manager.createRealm("original");
+        application = realm.addApplication("application");
+        application.setBaseUrl("http://base");
+        application.setManagementUrl("http://management");
+        application.setName("app-name");
+        application.addRole("role-1");
+        application.addRole("role-2");
+
+        application.getApplicationUser().addRedirectUri("redirect-1");
+        application.getApplicationUser().addRedirectUri("redirect-2");
+
+        application.updateApplication();
+    }
+
+    @After
+    public void after() throws Exception {
+        identitySession.getTransaction().commit();
+        identitySession.close();
+        factory.close();
+    }
+
+    @Test
+    public void persist() {
+        RealmModel persisted = manager.getRealm(realm.getId());
+
+        assertEquals(application, persisted.getApplications().get(0));
+    }
+
+    @Test
+    public void json() {
+        ApplicationRepresentation representation = appManager.toRepresentation(application);
+
+        RealmModel realm = manager.createRealm("copy");
+        ApplicationModel copy = appManager.createApplication(realm, representation);
+
+        assertEquals(application, copy);
+    }
+
+    public static void assertEquals(ApplicationModel expected, ApplicationModel actual) {
+        Assert.assertEquals(expected.getName(), actual.getName());
+        Assert.assertEquals(expected.getBaseUrl(), actual.getBaseUrl());
+        Assert.assertEquals(expected.getManagementUrl(), actual.getManagementUrl());
+
+        UserModel auser = actual.getApplicationUser();
+        UserModel euser = expected.getApplicationUser();
+
+        Assert.assertTrue(euser.getRedirectUris().containsAll(auser.getRedirectUris()));
+    }
+
+    public static void assertEquals(List<RoleModel> expected, List<RoleModel> actual) {
+        Assert.assertEquals(expected.size(), actual.size());
+        Iterator<RoleModel> exp = expected.iterator();
+        Iterator<RoleModel> act = actual.iterator();
+        while (exp.hasNext()) {
+            Assert.assertEquals(exp.next().getName(), act.next().getName());
+        }
+    }
+
+}
+
diff --git a/services/src/test/java/org/keycloak/test/UserModelTest.java b/services/src/test/java/org/keycloak/test/UserModelTest.java
new file mode 100644
index 0000000..9922596
--- /dev/null
+++ b/services/src/test/java/org/keycloak/test/UserModelTest.java
@@ -0,0 +1,118 @@
+package org.keycloak.test;
+
+import java.util.Iterator;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserModel.RequiredAction;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.services.resources.KeycloakApplication;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class UserModelTest extends AbstractKeycloakServerTest {
+    private KeycloakSessionFactory factory;
+    private KeycloakSession identitySession;
+    private RealmManager manager;
+
+    @Before
+    public void before() throws Exception {
+        factory = KeycloakApplication.buildSessionFactory();
+        identitySession = factory.createSession();
+        identitySession.getTransaction().begin();
+        manager = new RealmManager(identitySession);
+    }
+
+    @After
+    public void after() throws Exception {
+        identitySession.getTransaction().commit();
+        identitySession.close();
+        factory.close();
+    }
+
+    @Test
+    public void persistUser() {
+        RealmModel realm = manager.createRealm("original");
+        UserModel user = realm.addUser("user");
+        user.setFirstName("first-name");
+        user.setLastName("last-name");
+        user.setEmail("email");
+
+        user.addRedirectUri("redirect-1");
+        user.addRedirectUri("redirect-2");
+
+        user.addRequiredAction(RequiredAction.CONFIGURE_TOTP);
+        user.addRequiredAction(RequiredAction.UPDATE_PASSWORD);
+
+        UserModel persisted = manager.getRealm(realm.getId()).getUser("user");
+
+        assertEquals(user, persisted);
+    }
+
+    @Test
+    public void testUserRequiredActions() throws Exception {
+        RealmModel realm = manager.createRealm("original");
+        UserModel user = realm.addUser("user");
+
+        Assert.assertTrue(user.getRequiredActions().isEmpty());
+
+        user.addRequiredAction(UserModel.RequiredAction.CONFIGURE_TOTP);
+        user = realm.getUser("user");
+
+        Assert.assertEquals(1, user.getRequiredActions().size());
+        Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.CONFIGURE_TOTP));
+
+        user.addRequiredAction(UserModel.RequiredAction.CONFIGURE_TOTP);
+        user = realm.getUser("user");
+
+        Assert.assertEquals(1, user.getRequiredActions().size());
+        Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.CONFIGURE_TOTP));
+
+        user.addRequiredAction(UserModel.RequiredAction.VERIFY_EMAIL);
+        user = realm.getUser("user");
+
+        Assert.assertEquals(2, user.getRequiredActions().size());
+        Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.CONFIGURE_TOTP));
+        Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL));
+
+        user.removeRequiredAction(UserModel.RequiredAction.CONFIGURE_TOTP);
+        user = realm.getUser("user");
+
+        Assert.assertEquals(1, user.getRequiredActions().size());
+        Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL));
+
+        user.removeRequiredAction(UserModel.RequiredAction.VERIFY_EMAIL);
+        user = realm.getUser("user");
+
+        Assert.assertTrue(user.getRequiredActions().isEmpty());
+    }
+
+    public static void assertEquals(UserModel expected, UserModel actual) {
+        Assert.assertEquals(expected.getLoginName(), actual.getLoginName());
+        Assert.assertEquals(expected.getFirstName(), actual.getFirstName());
+        Assert.assertEquals(expected.getLastName(), actual.getLastName());
+        Assert.assertArrayEquals(expected.getRedirectUris().toArray(), actual.getRedirectUris().toArray());
+        Assert.assertArrayEquals(expected.getRequiredActions().toArray(), actual.getRequiredActions().toArray());
+
+    }
+
+    public static void assertEquals(List<RoleModel> expected, List<RoleModel> actual) {
+        Assert.assertEquals(expected.size(), actual.size());
+        Iterator<RoleModel> exp = expected.iterator();
+        Iterator<RoleModel> act = actual.iterator();
+        while (exp.hasNext()) {
+            Assert.assertEquals(exp.next().getName(), act.next().getName());
+        }
+    }
+
+}
+
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 e6a89bd..2ccd506 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
@@ -28,8 +28,13 @@ 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.models.UserModel;
+import org.keycloak.services.managers.RealmManager;
 import org.keycloak.testsuite.OAuthClient;
 import org.keycloak.testsuite.OAuthClient.AuthorizationCodeResponse;
+import org.keycloak.testsuite.pages.ErrorPage;
 import org.keycloak.testsuite.pages.LoginPage;
 import org.keycloak.testsuite.rule.KeycloakRule;
 import org.keycloak.testsuite.rule.WebResource;
@@ -56,8 +61,11 @@ public class AuthorizationCodeTest {
     @WebResource
     protected LoginPage loginPage;
 
+    @WebResource
+    protected ErrorPage errorPage;
+
     @Test
-    public void authorizationRequest() throws ClientProtocolException, IOException {
+    public void authorizationRequest() throws IOException {
         oauth.state("mystate");
 
         AuthorizationCodeResponse response = oauth.doLogin("test-user@localhost", "password");
@@ -69,7 +77,54 @@ public class AuthorizationCodeTest {
     }
 
     @Test
-    public void authorizationRequestNoState() throws ClientProtocolException, IOException {
+    public void authorizationValidRedirectUri() 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.state("mystate");
+
+        AuthorizationCodeResponse response = oauth.doLogin("test-user@localhost", "password");
+
+        Assert.assertTrue(response.isRedirected());
+        Assert.assertNotNull(response.getCode());
+    }
+
+    @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");
 
         Assert.assertTrue(response.isRedirected());
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/ErrorPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/ErrorPage.java
new file mode 100644
index 0000000..bfe3201
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/ErrorPage.java
@@ -0,0 +1,54 @@
+/*
+ * 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.pages;
+
+import org.keycloak.testsuite.OAuthClient;
+import org.keycloak.testsuite.rule.WebResource;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ErrorPage extends Page {
+
+    @WebResource
+    protected OAuthClient oauth;
+
+    @FindBy(id = "error-summary")
+    private WebElement errorMessage;
+
+    public String getError() {
+        return errorMessage.getText();
+    }
+
+    public boolean isCurrent() {
+        return driver.getTitle().equals("We're sorry...");
+    }
+
+    @Override
+    public void open() {
+        throw new UnsupportedOperationException();
+    }
+
+}