diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-detail.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-detail.html index 2a21202efc..6d1ffa170d 100755 --- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-detail.html +++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-detail.html @@ -46,6 +46,14 @@ +
+ + +
+ +
+
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-list.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-list.html index d472c613d3..16a16972e1 100755 --- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-list.html +++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-list.html @@ -36,11 +36,12 @@ Application Name Enabled + Base URL - +
First page1-8 of 10 {{app.name}} {{app.enabled}} + {{app.baseUrl || "Not defined"}} 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 8f7d2ab9a9..559ebc2f70 100755 --- a/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/ApplicationRepresentation.java @@ -12,6 +12,7 @@ public class ApplicationRepresentation { protected String id; protected String name; protected String adminUrl; + protected String baseUrl; protected boolean surrogateAuthRequired; protected boolean enabled; protected List credentials; @@ -89,6 +90,14 @@ public class ApplicationRepresentation { this.adminUrl = adminUrl; } + public String getBaseUrl() { + return baseUrl; + } + + public void setBaseUrl(String baseUrl) { + this.baseUrl = baseUrl; + } + public List getCredentials() { return credentials; } diff --git a/core/src/main/java/org/keycloak/representations/idm/OAuthClientRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/OAuthClientRepresentation.java index cf46b1eac7..7a4cc694fe 100755 --- a/core/src/main/java/org/keycloak/representations/idm/OAuthClientRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/OAuthClientRepresentation.java @@ -10,6 +10,7 @@ import java.util.List; public class OAuthClientRepresentation { protected String id; protected String name; + protected String baseUrl; protected List redirectUris; protected List webOrigins; protected boolean enabled; @@ -39,6 +40,14 @@ public class OAuthClientRepresentation { this.enabled = enabled; } + public String getBaseUrl() { + return baseUrl; + } + + public void setBaseUrl(String baseUrl) { + this.baseUrl = baseUrl; + } + public List getRedirectUris() { return redirectUris; } diff --git a/forms/src/main/java/org/keycloak/forms/UrlBean.java b/forms/src/main/java/org/keycloak/forms/UrlBean.java index 49635a0cee..579659d930 100755 --- a/forms/src/main/java/org/keycloak/forms/UrlBean.java +++ b/forms/src/main/java/org/keycloak/forms/UrlBean.java @@ -36,6 +36,8 @@ public class UrlBean { private boolean socialRegistration; + private String referrerURI; + public boolean isSocialRegistration() { return socialRegistration; } @@ -44,9 +46,10 @@ public class UrlBean { this.socialRegistration = socialRegistration; } - public UrlBean(RealmBean realm, URI baseURI){ + public UrlBean(RealmBean realm, URI baseURI, String referrerURI){ this.realm = realm; this.baseURI = baseURI; + this.referrerURI = referrerURI; } public RealmBean getRealm() { @@ -136,4 +139,8 @@ public class UrlBean { return Urls.loginActionEmailVerification(baseURI, realm.getId()).toString(); } + public String getReferrerURI() { + return referrerURI; + } + } diff --git a/forms/src/main/java/org/keycloak/service/FormServiceImpl.java b/forms/src/main/java/org/keycloak/service/FormServiceImpl.java index 28ed554e08..329dbf2d0a 100755 --- a/forms/src/main/java/org/keycloak/service/FormServiceImpl.java +++ b/forms/src/main/java/org/keycloak/service/FormServiceImpl.java @@ -35,6 +35,7 @@ import org.keycloak.forms.TemplateBean; import org.keycloak.forms.TotpBean; import org.keycloak.forms.UrlBean; import org.keycloak.forms.UserBean; +import org.keycloak.models.ApplicationModel; import org.keycloak.services.FormService; import org.keycloak.services.resources.flows.Pages; @@ -54,22 +55,22 @@ public class FormServiceImpl implements FormService { private static final String ID = "FormServiceId"; private static final String BUNDLE = "org.keycloak.forms.messages"; - private final Map commandMap = new HashMap(); + private final Map commandMap = new HashMap(); public FormServiceImpl(){ commandMap.put(Pages.LOGIN, new CommandLogin()); commandMap.put(Pages.REGISTER, new CommandRegister()); - commandMap.put(Pages.ACCOUNT, new CommandAccount()); - commandMap.put(Pages.LOGIN_UPDATE_PROFILE, new CommandPassword()); - commandMap.put(Pages.PASSWORD, new CommandPassword()); - commandMap.put(Pages.LOGIN_RESET_PASSWORD, new CommandPassword()); - commandMap.put(Pages.LOGIN_UPDATE_PASSWORD, new CommandPassword()); - commandMap.put(Pages.ACCESS, new CommandAccess()); - commandMap.put(Pages.SOCIAL, new CommandSocial()); + commandMap.put(Pages.ACCOUNT, new CommandCommon()); + commandMap.put(Pages.LOGIN_UPDATE_PROFILE, new CommandCommon()); + commandMap.put(Pages.PASSWORD, new CommandCommon()); + commandMap.put(Pages.LOGIN_RESET_PASSWORD, new CommandCommon()); + commandMap.put(Pages.LOGIN_UPDATE_PASSWORD, new CommandCommon()); + commandMap.put(Pages.ACCESS, new CommandCommon()); + commandMap.put(Pages.SOCIAL, new CommandCommon()); commandMap.put(Pages.TOTP, new CommandTotp()); commandMap.put(Pages.LOGIN_CONFIG_TOTP, new CommandTotp()); commandMap.put(Pages.LOGIN_TOTP, new CommandLoginTotp()); - commandMap.put(Pages.LOGIN_VERIFY_EMAIL, new CommandVerifyEmail()); + commandMap.put(Pages.LOGIN_VERIFY_EMAIL, new CommandCommon()); commandMap.put(Pages.OAUTH_GRANT, new CommandOAuthGrant()); } @@ -117,121 +118,69 @@ public class FormServiceImpl implements FormService { return out.toString(); } - private class CommandTotp implements Command { + private class CommandCommon { + protected RealmBean realm; + protected UrlBean url; + protected UserBean user; + protected LoginBean login; + public void exec(Map attributes, FormServiceDataBean dataBean) { - RealmBean realm = new RealmBean(dataBean.getRealm()); + realm = new RealmBean(dataBean.getRealm()); + + String referrer = dataBean.getQueryParam("referrer"); + String referrerUri = null; + if (referrer != null) { + for (ApplicationModel a : dataBean.getRealm().getApplications()) { + if (a.getName().equals(referrer)) { + referrerUri = a.getBaseUrl(); + break; + } + } + } + + url = new UrlBean(realm, dataBean.getBaseURI(), referrerUri); + url.setSocialRegistration(dataBean.getSocialRegistration()); + user = new UserBean(dataBean.getUserModel()); + login = new LoginBean(realm, dataBean.getFormData()); attributes.put("realm", realm); - attributes.put("url", new UrlBean(realm, dataBean.getBaseURI())); - - UserBean user = new UserBean(dataBean.getUserModel()); + attributes.put("url", url); attributes.put("user", user); - - TotpBean totp = new TotpBean(user, dataBean.getContextPath()); - attributes.put("totp", totp); - - attributes.put("login", new LoginBean(realm, dataBean.getFormData())); + attributes.put("login", login); } } - private class CommandSocial implements Command { + private class CommandTotp extends CommandCommon { public void exec(Map attributes, FormServiceDataBean dataBean) { - RealmBean realm = new RealmBean(dataBean.getRealm()); - attributes.put("user", new UserBean(dataBean.getUserModel())); - attributes.put("url", new UrlBean(realm, dataBean.getBaseURI())); + super.exec(attributes, dataBean); + + attributes.put("totp", new TotpBean(user, dataBean.getContextPath())); } } - private class CommandEmail implements Command { + private class CommandLoginTotp extends CommandCommon { public void exec(Map attributes, FormServiceDataBean dataBean) { - } - } - - private class CommandPassword implements Command { - public void exec(Map attributes, FormServiceDataBean dataBean) { - RealmBean realm = new RealmBean(dataBean.getRealm()); - - attributes.put("realm", realm); - attributes.put("url", new UrlBean(realm, dataBean.getBaseURI())); - attributes.put("user", new UserBean(dataBean.getUserModel())); - attributes.put("login", new LoginBean(realm, dataBean.getFormData())); - } - } - - private class CommandLoginTotp implements Command { - public void exec(Map attributes, FormServiceDataBean dataBean) { - - RealmBean realm = new RealmBean(dataBean.getRealm()); - - attributes.put("realm", realm); - - UrlBean url = new UrlBean(realm, dataBean.getBaseURI()); - url.setSocialRegistration(dataBean.getSocialRegistration()); - - attributes.put("url", url); - attributes.put("user", new UserBean(dataBean.getUserModel())); - attributes.put("login", new LoginBean(realm, dataBean.getFormData())); + super.exec(attributes, dataBean); RegisterBean register = new RegisterBean(dataBean.getFormData(), dataBean.getSocialRegistration()); - SocialBean social = new SocialBean(realm, dataBean.getSocialProviders(), register, url); attributes.put("social", social); } } - private class CommandAccess implements Command { + private class CommandLogin extends CommandCommon { public void exec(Map attributes, FormServiceDataBean dataBean) { - RealmBean realm = new RealmBean(dataBean.getRealm()); - - attributes.put("realm", realm); - attributes.put("user", new UserBean(dataBean.getUserModel())); - attributes.put("url", new UrlBean(realm, dataBean.getBaseURI())); - } - } - - private class CommandAccount implements Command { - public void exec(Map attributes, FormServiceDataBean dataBean) { - RealmBean realm = new RealmBean(dataBean.getRealm()); - - attributes.put("realm", realm); - attributes.put("url", new UrlBean(realm, dataBean.getBaseURI())); - attributes.put("user", new UserBean(dataBean.getUserModel())); - attributes.put("login", new LoginBean(realm, dataBean.getFormData())); - } - } - - private class CommandLogin implements Command { - public void exec(Map attributes, FormServiceDataBean dataBean) { - RealmBean realm = new RealmBean(dataBean.getRealm()); - - attributes.put("realm", realm); - - UrlBean url = new UrlBean(realm, dataBean.getBaseURI()); - url.setSocialRegistration(dataBean.getSocialRegistration()); - - attributes.put("url", url); - attributes.put("user", new UserBean(dataBean.getUserModel())); - attributes.put("login", new LoginBean(realm, dataBean.getFormData())); + super.exec(attributes, dataBean); RegisterBean register = new RegisterBean(dataBean.getFormData(), dataBean.getSocialRegistration()); - SocialBean social = new SocialBean(realm, dataBean.getSocialProviders(), register, url); attributes.put("social", social); } } - private class CommandRegister implements Command { + private class CommandRegister extends CommandCommon { public void exec(Map attributes, FormServiceDataBean dataBean) { - - RealmBean realm = new RealmBean(dataBean.getRealm()); - - attributes.put("realm", realm); - - UrlBean url = new UrlBean(realm, dataBean.getBaseURI()); - url.setSocialRegistration(dataBean.getSocialRegistration()); - - attributes.put("url", url); - attributes.put("user", new UserBean(dataBean.getUserModel())); + super.exec(attributes, dataBean); RegisterBean register = new RegisterBean(dataBean.getFormData(), dataBean.getSocialRegistration()); attributes.put("register", register); @@ -241,8 +190,9 @@ public class FormServiceImpl implements FormService { } } - private class CommandOAuthGrant implements Command { + private class CommandOAuthGrant extends CommandCommon { public void exec(Map attributes, FormServiceDataBean dataBean) { + super.exec(attributes, dataBean); OAuthGrantBean oauth = new OAuthGrantBean(); oauth.setAction(dataBean.getOAuthAction()); @@ -255,22 +205,4 @@ public class FormServiceImpl implements FormService { } } - private class CommandVerifyEmail implements Command { - public void exec(Map attributes, FormServiceDataBean dataBean) { - - RealmBean realm = new RealmBean(dataBean.getRealm()); - - attributes.put("realm", realm); - - UrlBean url = new UrlBean(realm, dataBean.getBaseURI()); - url.setSocialRegistration(dataBean.getSocialRegistration()); - - attributes.put("url", url); - } - } - - private interface Command { - public void exec(Map attributes, FormServiceDataBean dataBean); - } - } \ No newline at end of file diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/account.ftl b/forms/src/main/resources/META-INF/resources/forms/theme/default/account.ftl index 4e7caac665..cdd5feff97 100755 --- a/forms/src/main/resources/META-INF/resources/forms/theme/default/account.ftl +++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/account.ftl @@ -27,7 +27,7 @@
- <#--a href="#">« Back to my application + <#if url.referrerURI??>Back to application
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/password.ftl b/forms/src/main/resources/META-INF/resources/forms/theme/default/password.ftl index 5c48264bb0..b92f3e651b 100755 --- a/forms/src/main/resources/META-INF/resources/forms/theme/default/password.ftl +++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/password.ftl @@ -23,7 +23,7 @@
- <#--a href="#">« Back to my application + <#if url.referrerURI??>Back to application
diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/totp.ftl b/forms/src/main/resources/META-INF/resources/forms/theme/default/totp.ftl index 5f4d6dc929..cad065e913 100755 --- a/forms/src/main/resources/META-INF/resources/forms/theme/default/totp.ftl +++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/totp.ftl @@ -54,6 +54,7 @@
+ <#if url.referrerURI??>Back to application
diff --git a/model/api/src/main/java/org/keycloak/models/ApplicationModel.java b/model/api/src/main/java/org/keycloak/models/ApplicationModel.java index 9039f4f70e..e5374b8280 100755 --- a/model/api/src/main/java/org/keycloak/models/ApplicationModel.java +++ b/model/api/src/main/java/org/keycloak/models/ApplicationModel.java @@ -29,6 +29,10 @@ public interface ApplicationModel extends RoleContainerModel, RoleMapperModel, S void setManagementUrl(String url); + String getBaseUrl(); + + void setBaseUrl(String url); + List getDefaultRoles(); void addDefaultRole(String name); diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java index ad5f9e18f0..c196edd3b0 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java @@ -82,6 +82,16 @@ public class ApplicationAdapter implements ApplicationModel { application.setManagementUrl(url); } + @Override + public String getBaseUrl() { + return application.getBaseUrl(); + } + + @Override + public void setBaseUrl(String url) { + application.setBaseUrl(url); + } + @Override public RoleModel getRole(String name) { Collection roles = application.getRoles(); diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ApplicationEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ApplicationEntity.java index c8a7400114..c0e21446a0 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ApplicationEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ApplicationEntity.java @@ -25,6 +25,7 @@ public class ApplicationEntity { private String name; private boolean enabled; private boolean surrogateAuthRequired; + private String baseUrl; private String managementUrl; @OneToOne(fetch = FetchType.EAGER) @@ -58,6 +59,14 @@ public class ApplicationEntity { this.surrogateAuthRequired = surrogateAuthRequired; } + public String getBaseUrl() { + return baseUrl; + } + + public void setBaseUrl(String baseUrl) { + this.baseUrl = baseUrl; + } + public String getManagementUrl() { return managementUrl; } diff --git a/model/picketlink/src/main/java/org/keycloak/models/picketlink/ApplicationAdapter.java b/model/picketlink/src/main/java/org/keycloak/models/picketlink/ApplicationAdapter.java index e6fe85b746..98a510a638 100755 --- a/model/picketlink/src/main/java/org/keycloak/models/picketlink/ApplicationAdapter.java +++ b/model/picketlink/src/main/java/org/keycloak/models/picketlink/ApplicationAdapter.java @@ -105,6 +105,17 @@ public class ApplicationAdapter implements ApplicationModel { updateApplication(); } + @Override + public String getBaseUrl() { + return applicationData.getBaseUrl(); + } + + @Override + public void setBaseUrl(String url) { + applicationData.setBaseUrl(url); + updateApplication(); + } + @Override public RoleAdapter getRole(String name) { Role role = SampleModel.getRole(getIdm(), name); diff --git a/model/picketlink/src/main/java/org/keycloak/models/picketlink/mappings/ApplicationData.java b/model/picketlink/src/main/java/org/keycloak/models/picketlink/mappings/ApplicationData.java index 3adb8f3c7b..ec4b4e7aca 100755 --- a/model/picketlink/src/main/java/org/keycloak/models/picketlink/mappings/ApplicationData.java +++ b/model/picketlink/src/main/java/org/keycloak/models/picketlink/mappings/ApplicationData.java @@ -13,6 +13,7 @@ public class ApplicationData extends AbstractPartition { private boolean enabled; private boolean surrogateAuthRequired; private String managementUrl; + private String baseUrl; private User resourceUser; private String[] defaultRoles; @@ -58,6 +59,15 @@ public class ApplicationData extends AbstractPartition { this.surrogateAuthRequired = surrogateAuthRequired; } + @AttributeProperty + public String getBaseUrl() { + return baseUrl; + } + + public void setBaseUrl(String baseUrl) { + this.baseUrl = baseUrl; + } + @AttributeProperty public String getManagementUrl() { return managementUrl; diff --git a/model/picketlink/src/main/java/org/keycloak/models/picketlink/mappings/ApplicationEntity.java b/model/picketlink/src/main/java/org/keycloak/models/picketlink/mappings/ApplicationEntity.java index 88af1a6945..8e450a72e7 100755 --- a/model/picketlink/src/main/java/org/keycloak/models/picketlink/mappings/ApplicationEntity.java +++ b/model/picketlink/src/main/java/org/keycloak/models/picketlink/mappings/ApplicationEntity.java @@ -31,6 +31,8 @@ public class ApplicationEntity implements Serializable { private boolean surrogateAuthRequired; @AttributeValue private String managementUrl; + @AttributeValue + private String baseUrl; @AttributeValue private String[] defaultRoles; diff --git a/services/src/main/java/org/keycloak/services/FormService.java b/services/src/main/java/org/keycloak/services/FormService.java index 9a357a3c15..716e76bfb5 100755 --- a/services/src/main/java/org/keycloak/services/FormService.java +++ b/services/src/main/java/org/keycloak/services/FormService.java @@ -53,6 +53,7 @@ public interface FormService { private FormFlows.MessageType messageType; private MultivaluedMap formData; + private Map queryParams; private URI baseURI; private List socialProviders; @@ -87,10 +88,11 @@ public interface FormService { private String contextPath; - public FormServiceDataBean(RealmModel realm, UserModel userModel, MultivaluedMap formData, String message) { + public FormServiceDataBean(RealmModel realm, UserModel userModel, MultivaluedMap formData, Map queryParams, String message) { this.realm = realm; this.userModel = userModel; this.formData = formData; + this.queryParams = queryParams; this.message = message; socialProviders = new LinkedList(); @@ -125,6 +127,16 @@ public interface FormService { return formData; } + public Map getQueryParams() { + return queryParams; + } + + + public String getQueryParam(String key) { + return queryParams != null ? queryParams.get(key) : null; + } + + public void setFormData(MultivaluedMap formData) { this.formData = formData; } 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 e3c3322d4e..1e5a24f1a9 100755 --- a/services/src/main/java/org/keycloak/services/managers/ApplicationManager.java +++ b/services/src/main/java/org/keycloak/services/managers/ApplicationManager.java @@ -50,6 +50,7 @@ public class ApplicationManager { applicationModel.setEnabled(resourceRep.isEnabled()); applicationModel.setManagementUrl(resourceRep.getAdminUrl()); applicationModel.setSurrogateAuthRequired(resourceRep.isSurrogateAuthRequired()); + applicationModel.setBaseUrl(resourceRep.getBaseUrl()); applicationModel.updateApplication(); UserModel resourceUser = applicationModel.getApplicationUser(); @@ -128,6 +129,7 @@ public class ApplicationManager { resource.setName(rep.getName()); resource.setEnabled(rep.isEnabled()); resource.setManagementUrl(rep.getAdminUrl()); + resource.setBaseUrl(rep.getBaseUrl()); resource.setSurrogateAuthRequired(rep.isSurrogateAuthRequired()); resource.updateApplication(); @@ -153,6 +155,7 @@ public class ApplicationManager { rep.setEnabled(applicationModel.isEnabled()); rep.setAdminUrl(applicationModel.getManagementUrl()); rep.setSurrogateAuthRequired(applicationModel.isSurrogateAuthRequired()); + rep.setBaseUrl(applicationModel.getBaseUrl()); Set redirectUris = applicationModel.getApplicationUser().getRedirectUris(); if (redirectUris != null) { diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java index 584ef1c326..22e726985b 100755 --- a/services/src/main/java/org/keycloak/services/resources/AccountService.java +++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java @@ -46,6 +46,7 @@ import javax.ws.rs.*; import javax.ws.rs.core.*; import javax.ws.rs.ext.Providers; import java.net.URI; +import java.net.URISyntaxException; import java.util.List; /** @@ -89,7 +90,15 @@ public class AccountService { if (!hasAccess(auth)) { return noAccess(); } - return Flows.forms(realm, request, uriInfo).setUser(auth.getUser()).forwardToForm(template); + + FormFlows forms = Flows.forms(realm, request, uriInfo).setUser(auth.getUser()); + + String referrer = getReferrer(); + if (referrer != null) { + forms.setQueryParam("referrer", referrer); + } + + return forms.forwardToForm(template); } else { return login(path); } @@ -321,11 +330,8 @@ public class AccountService { throw new BadRequestException(); } - UriBuilder redirectBuilder = Urls.accountBase(uriInfo.getBaseUri()); - if (path != null) { - redirectBuilder.path(path); - } - URI redirectUri = redirectBuilder.build(realm.getId()); + URI accountUri = Urls.accountBase(uriInfo.getBaseUri()).path("/").build(realm.getId()); + URI redirectUri = path != null ? accountUri.resolve(path) : accountUri; NewCookie cookie = authManager.createAccountIdentityCookie(realm, accessCode.getUser(), client, Urls.accountBase(uriInfo.getBaseUri()).build(realm.getId())); return Response.status(302).cookie(cookie).location(redirectUri).build(); @@ -353,6 +359,11 @@ public class AccountService { URI accountUri = Urls.accountPageBuilder(uriInfo.getBaseUri()).path(AccountService.class, "loginRedirect").build(realm.getId()); + String referrer = getReferrer(); + if (referrer != null) { + path = (path != null ? path : "") + "?referrer=" + referrer; + } + oauth.setStateCookiePath(accountUri.getPath()); return oauth.redirect(uriInfo, accountUri.toString(), path); } @@ -393,4 +404,23 @@ public class AccountService { return application.hasRole(user, role); } + private String getReferrer() { + String referrer = uriInfo.getQueryParameters().getFirst("referrer"); + if (referrer != null) { + return referrer; + } + + String referrerUrl = headers.getHeaderString("Referer"); + if (referrerUrl != null) { + for (ApplicationModel a : realm.getApplications()) { + if (a.getBaseUrl() != null && referrerUrl.startsWith(a.getBaseUrl())) { + return a.getName(); + } + } + return null; + } + + return null; + } + } diff --git a/services/src/main/java/org/keycloak/services/resources/flows/FormFlows.java b/services/src/main/java/org/keycloak/services/resources/flows/FormFlows.java index 3dcd6b5db6..e95c3aa094 100755 --- a/services/src/main/java/org/keycloak/services/resources/flows/FormFlows.java +++ b/services/src/main/java/org/keycloak/services/resources/flows/FormFlows.java @@ -39,18 +39,16 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriInfo; import java.net.URI; +import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; /** * @author Stian Thorgersen */ public class FormFlows { - public static final String DATA = "KEYCLOAK_FORMS_DATA"; - public static final String ERROR_MESSAGE = "KEYCLOAK_FORMS_ERROR_MESSAGE"; - public static final String USER = UserModel.class.getName(); - public static final String SOCIAL_REGISTRATION = "socialRegistration"; public static final String CODE = "code"; // TODO refactor/rename "error" to "message" everywhere where it makes sense @@ -61,6 +59,8 @@ public class FormFlows { private MultivaluedMap formData; + private Map queryParams; + private RealmModel realm; private HttpRequest request; @@ -118,6 +118,12 @@ public class FormFlows { uriBuilder.queryParam(CODE, accessCode.getCode()); } + if (queryParams != null) { + for (Map.Entry q : queryParams.entrySet()) { + uriBuilder.replaceQueryParam(q.getKey(), q.getValue()); + } + } + URI baseURI = uriBuilder.build(); formDataBean.setBaseURI(baseURI); @@ -140,7 +146,7 @@ public class FormFlows { public Response forwardToForm(String template) { - FormService.FormServiceDataBean formDataBean = new FormService.FormServiceDataBean(realm, userModel, formData, error); + FormService.FormServiceDataBean formDataBean = new FormService.FormServiceDataBean(realm, userModel, formData, queryParams, error); formDataBean.setMessageType(messageType); return forwardToForm(template, formDataBean); @@ -192,7 +198,7 @@ public class FormFlows { public Response forwardToOAuthGrant(){ - FormService.FormServiceDataBean formDataBean = new FormService.FormServiceDataBean(realm, userModel, formData, error); + FormService.FormServiceDataBean formDataBean = new FormService.FormServiceDataBean(realm, userModel, formData, queryParams, error); formDataBean.setOAuthRealmRolesRequested((List) request.getAttribute("realmRolesRequested")); formDataBean.setOAuthResourceRolesRequested((MultivaluedMap) request.getAttribute("resourceRolesRequested")); @@ -208,6 +214,14 @@ public class FormFlows { return this; } + public FormFlows setQueryParam(String key, String value) { + if (queryParams == null) { + queryParams = new HashMap(); + } + queryParams.put(key, value); + return this; + } + public FormFlows setError(String error) { this.error = error; return this; diff --git a/services/src/test/java/org/keycloak/test/ApplicationModelTest.java b/services/src/test/java/org/keycloak/test/ApplicationModelTest.java index f96e08976a..e32248195c 100755 --- a/services/src/test/java/org/keycloak/test/ApplicationModelTest.java +++ b/services/src/test/java/org/keycloak/test/ApplicationModelTest.java @@ -40,6 +40,7 @@ public class ApplicationModelTest extends AbstractKeycloakServerTest { realm = manager.createRealm("original"); application = realm.addApplication("application"); + application.setBaseUrl("http://base"); application.setManagementUrl("http://management"); application.setName("app-name"); application.addRole("role-1"); @@ -82,6 +83,7 @@ public class ApplicationModelTest extends AbstractKeycloakServerTest { 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()); Assert.assertEquals(expected.getDefaultRoles(), actual.getDefaultRoles()); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/ApplicationServlet.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/ApplicationServlet.java index 9aa9655696..996085e2f0 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/ApplicationServlet.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/ApplicationServlet.java @@ -39,23 +39,11 @@ import java.util.List; */ public class ApplicationServlet extends HttpServlet { + private static final String LINK = "%s"; + @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String title = ""; - String body = ""; - - StringBuffer sb = req.getRequestURL(); - sb.append("?"); - sb.append(req.getQueryString()); - - List query = null; - - try { - query = URLEncodedUtils.parse(new URI(sb.toString()), "UTF-8"); - } catch (URISyntaxException e) { - throw new ServletException(e); - } - if (req.getRequestURI().endsWith("auth")) { title = "AUTH_RESPONSE"; } else if (req.getRequestURI().endsWith("logout")) { @@ -65,7 +53,11 @@ public class ApplicationServlet extends HttpServlet { } PrintWriter pw = resp.getWriter(); - pw.printf("%s%s", title, body); + pw.printf("%s", title); + + pw.printf(LINK, "http://localhost:8081/auth-server/rest/realms/test/account", "account", "account"); + + pw.print(""); pw.flush(); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AccountTest.java index 4d0d3ca2e8..17d6348eff 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AccountTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AccountTest.java @@ -111,6 +111,28 @@ public class AccountTest { }); } + @Test + public void returnToAppFromHeader() { + appPage.open(); + appPage.openAccount(); + loginPage.login("test-user@localhost", "password"); + + Assert.assertTrue(profilePage.isCurrent()); + profilePage.backToApplication(); + + Assert.assertTrue(appPage.isCurrent()); + } + + @Test + public void returnToAppFromQueryParam() { + driver.navigate().to(AccountUpdateProfilePage.PATH + "?referrer=test-app"); + loginPage.login("test-user@localhost", "password"); + Assert.assertTrue(profilePage.isCurrent()); + profilePage.backToApplication(); + + Assert.assertTrue(appPage.isCurrent()); + } + @Test public void changePassword() { changePasswordPage.open(); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java index a9f64958bc..d19ca29c1b 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java @@ -30,7 +30,7 @@ import org.openqa.selenium.support.FindBy; */ public class AccountUpdateProfilePage extends AbstractAccountPage { - private static String PATH = Constants.AUTH_SERVER_ROOT + "/rest/realms/test/account"; + public static String PATH = Constants.AUTH_SERVER_ROOT + "/rest/realms/test/account"; @FindBy(id = "firstName") private WebElement firstNameInput; @@ -41,6 +41,10 @@ public class AccountUpdateProfilePage extends AbstractAccountPage { @FindBy(id = "email") private WebElement emailInput; + + @FindBy(linkText = "Back to application") + private WebElement backToApplicationLink; + @FindBy(css = "button[type=\"submit\"]") private WebElement submitButton; @@ -78,6 +82,10 @@ public class AccountUpdateProfilePage extends AbstractAccountPage { driver.navigate().to(PATH); } + public void backToApplication() { + backToApplicationLink.click(); + } + public boolean isSuccess(){ return feedbackMessage != null && "Success!".equals(feedbackMessage.getText()); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AppPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AppPage.java index 3faa197818..c0ce1397a4 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AppPage.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AppPage.java @@ -22,6 +22,9 @@ package org.keycloak.testsuite.pages; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.FindBy; + /** * @author Stian Thorgersen */ @@ -29,6 +32,9 @@ public class AppPage extends AbstractPage { private String baseUrl = "http://localhost:8081/app"; + @FindBy(id = "account") + private WebElement accountLink; + @Override public void open() { driver.navigate().to(baseUrl); @@ -43,6 +49,10 @@ public class AppPage extends AbstractPage { return RequestType.valueOf(driver.getTitle()); } + public void openAccount() { + accountLink.click(); + } + public enum RequestType { AUTH_RESPONSE, LOGOUT_REQUEST, APP_REQUEST } diff --git a/testsuite/integration/src/test/resources/testrealm.json b/testsuite/integration/src/test/resources/testrealm.json index 4122174383..cf8d8aefaa 100755 --- a/testsuite/integration/src/test/resources/testrealm.json +++ b/testsuite/integration/src/test/resources/testrealm.json @@ -72,6 +72,7 @@ { "name": "test-app", "enabled": true, + "baseUrl": "http://localhost:8081/app", "adminUrl": "http://localhost:8081/app/logout", "credentials": [ {