Added redirect uris to application

This commit is contained in:
Stian Thorgersen 2013-10-17 15:00:46 +01:00
parent 7966afb434
commit 79c1230a9d
12 changed files with 454 additions and 127 deletions

View file

@ -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;
}
}

View file

@ -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" >

View file

@ -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);

View file

@ -1,31 +0,0 @@
package org.keycloak.models.utils;
import java.lang.reflect.Array;
import java.util.Arrays;
public class ArrayUtils {
public static <T> T[] add(T[] src, T o) {
T[] dst = Arrays.copyOf(src, src.length + 1);
dst[src.length] = o;
return dst;
}
public static <T> T[] remove(T[] src, T o) {
int l = Arrays.binarySearch(src, o);
if (l < 0) {
return src;
}
T[] dst = newInstance(o, src.length - 1);
System.arraycopy(src, 0, dst, 0, l);
System.arraycopy(src, l + 1, dst, l, dst.length - l);
return dst;
}
@SuppressWarnings("unchecked")
private static <T extends Object> T[] newInstance(T type, int length) {
return (T[]) Array.newInstance(type.getClass(), length);
}
}

View file

@ -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);
}
}
Attribute<RequiredAction[]> a = new Attribute<RequiredAction[]>(REQUIRED_ACTIONS_ATTR, actions);
user.setAttribute(a);
idm.update(user);
addToAttributeSet(REQUIRED_ACTIONS_ATTR, action);
}
@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);
removeFromAttributeSet(REQUIRED_ACTIONS_ATTR, action);
}
idm.update(user);
@Override
public Set<String> getRedirectUris() {
return getAttributeSet(REDIRECT_URIS);
}
@Override
public void setRedirectUris(Set<String> redirectUris) {
setAttributeSet(REDIRECT_URIS, redirectUris);
}
@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);
}
}
}

View file

@ -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;
}

View file

@ -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);

View file

@ -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());
}
}

View file

@ -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());
}
}
}

View file

@ -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());
}
}
}

View file

@ -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());

View file

@ -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();
}
}