KEYCLOAK-181

Link back to application from account management pages
This commit is contained in:
Stian Thorgersen 2013-11-28 13:48:30 +00:00
parent 2059283e99
commit 85eeb415e1
25 changed files with 256 additions and 148 deletions

View file

@ -46,6 +46,14 @@
<label for="enabled" class="control-label">Enabled</label> <label for="enabled" class="control-label">Enabled</label>
<input ng-model="application.enabled" name="enabled" id="enabled" onoffswitch /> <input ng-model="application.enabled" name="enabled" id="enabled" onoffswitch />
</div> </div>
<div class="form-group">
<label for="baseUrl" class="control-label">Base URL</label>
<div class="controls">
<input class="input-small" type="text" name="baseUrl" id="baseUrl"
data-ng-model="application.baseUrl">
</div>
</div>
<div class="form-group"> <div class="form-group">
<label for="adminUrl" class="control-label">Admin URL</label> <label for="adminUrl" class="control-label">Admin URL</label>

View file

@ -36,11 +36,12 @@
<tr data-ng-show="applications.length > 0"> <tr data-ng-show="applications.length > 0">
<th>Application Name</th> <th>Application Name</th>
<th>Enabled</th> <th>Enabled</th>
<th>Base URL</th>
</tr> </tr>
</thead> </thead>
<tfoot data-ng-show="applications && applications.length > 5"> <!-- todo --> <tfoot data-ng-show="applications && applications.length > 5"> <!-- todo -->
<tr> <tr>
<td colspan="2"> <td colspan="3">
<div class="table-nav"> <div class="table-nav">
<a href="#" class="first disabled">First page</a><a href="#" class="prev disabled">Previous <a href="#" class="first disabled">First page</a><a href="#" class="prev disabled">Previous
page</a><span><strong>1-8</strong> of <strong>10</strong></span><a href="#" page</a><span><strong>1-8</strong> of <strong>10</strong></span><a href="#"
@ -54,6 +55,7 @@
<tr ng-repeat="app in applications"> <tr ng-repeat="app in applications">
<td><a href="#/realms/{{realm.id}}/applications/{{app.id}}">{{app.name}}</a></td> <td><a href="#/realms/{{realm.id}}/applications/{{app.id}}">{{app.name}}</a></td>
<td>{{app.enabled}}</td> <td>{{app.enabled}}</td>
<td ng-class="{'text-muted': !app.baseUrl}">{{app.baseUrl || "Not defined"}}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View file

@ -12,6 +12,7 @@ public class ApplicationRepresentation {
protected String id; protected String id;
protected String name; protected String name;
protected String adminUrl; protected String adminUrl;
protected String baseUrl;
protected boolean surrogateAuthRequired; protected boolean surrogateAuthRequired;
protected boolean enabled; protected boolean enabled;
protected List<CredentialRepresentation> credentials; protected List<CredentialRepresentation> credentials;
@ -89,6 +90,14 @@ public class ApplicationRepresentation {
this.adminUrl = adminUrl; this.adminUrl = adminUrl;
} }
public String getBaseUrl() {
return baseUrl;
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
public List<CredentialRepresentation> getCredentials() { public List<CredentialRepresentation> getCredentials() {
return credentials; return credentials;
} }

View file

@ -10,6 +10,7 @@ import java.util.List;
public class OAuthClientRepresentation { public class OAuthClientRepresentation {
protected String id; protected String id;
protected String name; protected String name;
protected String baseUrl;
protected List<String> redirectUris; protected List<String> redirectUris;
protected List<String> webOrigins; protected List<String> webOrigins;
protected boolean enabled; protected boolean enabled;
@ -39,6 +40,14 @@ public class OAuthClientRepresentation {
this.enabled = enabled; this.enabled = enabled;
} }
public String getBaseUrl() {
return baseUrl;
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
public List<String> getRedirectUris() { public List<String> getRedirectUris() {
return redirectUris; return redirectUris;
} }

View file

@ -36,6 +36,8 @@ public class UrlBean {
private boolean socialRegistration; private boolean socialRegistration;
private String referrerURI;
public boolean isSocialRegistration() { public boolean isSocialRegistration() {
return socialRegistration; return socialRegistration;
} }
@ -44,9 +46,10 @@ public class UrlBean {
this.socialRegistration = socialRegistration; this.socialRegistration = socialRegistration;
} }
public UrlBean(RealmBean realm, URI baseURI){ public UrlBean(RealmBean realm, URI baseURI, String referrerURI){
this.realm = realm; this.realm = realm;
this.baseURI = baseURI; this.baseURI = baseURI;
this.referrerURI = referrerURI;
} }
public RealmBean getRealm() { public RealmBean getRealm() {
@ -136,4 +139,8 @@ public class UrlBean {
return Urls.loginActionEmailVerification(baseURI, realm.getId()).toString(); return Urls.loginActionEmailVerification(baseURI, realm.getId()).toString();
} }
public String getReferrerURI() {
return referrerURI;
}
} }

View file

@ -35,6 +35,7 @@ import org.keycloak.forms.TemplateBean;
import org.keycloak.forms.TotpBean; import org.keycloak.forms.TotpBean;
import org.keycloak.forms.UrlBean; import org.keycloak.forms.UrlBean;
import org.keycloak.forms.UserBean; import org.keycloak.forms.UserBean;
import org.keycloak.models.ApplicationModel;
import org.keycloak.services.FormService; import org.keycloak.services.FormService;
import org.keycloak.services.resources.flows.Pages; 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 ID = "FormServiceId";
private static final String BUNDLE = "org.keycloak.forms.messages"; private static final String BUNDLE = "org.keycloak.forms.messages";
private final Map<String, Command> commandMap = new HashMap<String,Command>(); private final Map<String, CommandCommon> commandMap = new HashMap<String, CommandCommon>();
public FormServiceImpl(){ public FormServiceImpl(){
commandMap.put(Pages.LOGIN, new CommandLogin()); commandMap.put(Pages.LOGIN, new CommandLogin());
commandMap.put(Pages.REGISTER, new CommandRegister()); commandMap.put(Pages.REGISTER, new CommandRegister());
commandMap.put(Pages.ACCOUNT, new CommandAccount()); commandMap.put(Pages.ACCOUNT, new CommandCommon());
commandMap.put(Pages.LOGIN_UPDATE_PROFILE, new CommandPassword()); commandMap.put(Pages.LOGIN_UPDATE_PROFILE, new CommandCommon());
commandMap.put(Pages.PASSWORD, new CommandPassword()); commandMap.put(Pages.PASSWORD, new CommandCommon());
commandMap.put(Pages.LOGIN_RESET_PASSWORD, new CommandPassword()); commandMap.put(Pages.LOGIN_RESET_PASSWORD, new CommandCommon());
commandMap.put(Pages.LOGIN_UPDATE_PASSWORD, new CommandPassword()); commandMap.put(Pages.LOGIN_UPDATE_PASSWORD, new CommandCommon());
commandMap.put(Pages.ACCESS, new CommandAccess()); commandMap.put(Pages.ACCESS, new CommandCommon());
commandMap.put(Pages.SOCIAL, new CommandSocial()); commandMap.put(Pages.SOCIAL, new CommandCommon());
commandMap.put(Pages.TOTP, new CommandTotp()); commandMap.put(Pages.TOTP, new CommandTotp());
commandMap.put(Pages.LOGIN_CONFIG_TOTP, new CommandTotp()); commandMap.put(Pages.LOGIN_CONFIG_TOTP, new CommandTotp());
commandMap.put(Pages.LOGIN_TOTP, new CommandLoginTotp()); 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()); commandMap.put(Pages.OAUTH_GRANT, new CommandOAuthGrant());
} }
@ -117,121 +118,69 @@ public class FormServiceImpl implements FormService {
return out.toString(); 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<String, Object> attributes, FormServiceDataBean dataBean) { public void exec(Map<String, Object> 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("realm", realm);
attributes.put("url", new UrlBean(realm, dataBean.getBaseURI())); attributes.put("url", url);
UserBean user = new UserBean(dataBean.getUserModel());
attributes.put("user", user); attributes.put("user", user);
attributes.put("login", login);
TotpBean totp = new TotpBean(user, dataBean.getContextPath());
attributes.put("totp", totp);
attributes.put("login", new LoginBean(realm, dataBean.getFormData()));
} }
} }
private class CommandSocial implements Command { private class CommandTotp extends CommandCommon {
public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) { public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) {
RealmBean realm = new RealmBean(dataBean.getRealm()); super.exec(attributes, dataBean);
attributes.put("user", new UserBean(dataBean.getUserModel()));
attributes.put("url", new UrlBean(realm, dataBean.getBaseURI())); attributes.put("totp", new TotpBean(user, dataBean.getContextPath()));
} }
} }
private class CommandEmail implements Command { private class CommandLoginTotp extends CommandCommon {
public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) { public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) {
} super.exec(attributes, dataBean);
}
private class CommandPassword implements Command {
public void exec(Map<String, Object> 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<String, Object> 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()));
RegisterBean register = new RegisterBean(dataBean.getFormData(), dataBean.getSocialRegistration()); RegisterBean register = new RegisterBean(dataBean.getFormData(), dataBean.getSocialRegistration());
SocialBean social = new SocialBean(realm, dataBean.getSocialProviders(), register, url); SocialBean social = new SocialBean(realm, dataBean.getSocialProviders(), register, url);
attributes.put("social", social); attributes.put("social", social);
} }
} }
private class CommandAccess implements Command { private class CommandLogin extends CommandCommon {
public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) { public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) {
RealmBean realm = new RealmBean(dataBean.getRealm()); super.exec(attributes, dataBean);
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<String, Object> 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<String, Object> 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()));
RegisterBean register = new RegisterBean(dataBean.getFormData(), dataBean.getSocialRegistration()); RegisterBean register = new RegisterBean(dataBean.getFormData(), dataBean.getSocialRegistration());
SocialBean social = new SocialBean(realm, dataBean.getSocialProviders(), register, url); SocialBean social = new SocialBean(realm, dataBean.getSocialProviders(), register, url);
attributes.put("social", social); attributes.put("social", social);
} }
} }
private class CommandRegister implements Command { private class CommandRegister extends CommandCommon {
public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) { public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) {
super.exec(attributes, 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()));
RegisterBean register = new RegisterBean(dataBean.getFormData(), dataBean.getSocialRegistration()); RegisterBean register = new RegisterBean(dataBean.getFormData(), dataBean.getSocialRegistration());
attributes.put("register", register); 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<String, Object> attributes, FormServiceDataBean dataBean) { public void exec(Map<String, Object> attributes, FormServiceDataBean dataBean) {
super.exec(attributes, dataBean);
OAuthGrantBean oauth = new OAuthGrantBean(); OAuthGrantBean oauth = new OAuthGrantBean();
oauth.setAction(dataBean.getOAuthAction()); oauth.setAction(dataBean.getOAuthAction());
@ -255,22 +205,4 @@ public class FormServiceImpl implements FormService {
} }
} }
private class CommandVerifyEmail implements Command {
public void exec(Map<String, Object> 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<String, Object> attributes, FormServiceDataBean dataBean);
}
} }

View file

@ -27,7 +27,7 @@
</div> </div>
</fieldset> </fieldset>
<div class="form-actions"> <div class="form-actions">
<#--a href="#">« Back to my application</a--> <#if url.referrerURI??><a href="${url.referrerURI}">Back to application</a></#if>
<button type="submit" class="primary">Save</button> <button type="submit" class="primary">Save</button>
<button type="submit">Cancel</button> <button type="submit">Cancel</button>
</div> </div>

View file

@ -23,7 +23,7 @@
</div> </div>
</fieldset> </fieldset>
<div class="form-actions"> <div class="form-actions">
<#--a href="#">« Back to my application</a--> <#if url.referrerURI??><a href="${url.referrerURI}">Back to application</a></#if>
<button type="submit" class="primary">Save</button> <button type="submit" class="primary">Save</button>
<button type="submit">Cancel</button> <button type="submit">Cancel</button>
</div> </div>

View file

@ -54,6 +54,7 @@
<input type="hidden" id="totpSecret" name="totpSecret" value="${totp.totpSecret}" /> <input type="hidden" id="totpSecret" name="totpSecret" value="${totp.totpSecret}" />
</div> </div>
<div class="form-actions"> <div class="form-actions">
<#if url.referrerURI??><a href="${url.referrerURI}">Back to application</a></#if>
<button type="submit" class="primary">Submit</button> <button type="submit" class="primary">Submit</button>
</div> </div>
</form> </form>

View file

@ -29,6 +29,10 @@ public interface ApplicationModel extends RoleContainerModel, RoleMapperModel, S
void setManagementUrl(String url); void setManagementUrl(String url);
String getBaseUrl();
void setBaseUrl(String url);
List<String> getDefaultRoles(); List<String> getDefaultRoles();
void addDefaultRole(String name); void addDefaultRole(String name);

View file

@ -82,6 +82,16 @@ public class ApplicationAdapter implements ApplicationModel {
application.setManagementUrl(url); application.setManagementUrl(url);
} }
@Override
public String getBaseUrl() {
return application.getBaseUrl();
}
@Override
public void setBaseUrl(String url) {
application.setBaseUrl(url);
}
@Override @Override
public RoleModel getRole(String name) { public RoleModel getRole(String name) {
Collection<RoleEntity> roles = application.getRoles(); Collection<RoleEntity> roles = application.getRoles();

View file

@ -25,6 +25,7 @@ public class ApplicationEntity {
private String name; private String name;
private boolean enabled; private boolean enabled;
private boolean surrogateAuthRequired; private boolean surrogateAuthRequired;
private String baseUrl;
private String managementUrl; private String managementUrl;
@OneToOne(fetch = FetchType.EAGER) @OneToOne(fetch = FetchType.EAGER)
@ -58,6 +59,14 @@ public class ApplicationEntity {
this.surrogateAuthRequired = surrogateAuthRequired; this.surrogateAuthRequired = surrogateAuthRequired;
} }
public String getBaseUrl() {
return baseUrl;
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
public String getManagementUrl() { public String getManagementUrl() {
return managementUrl; return managementUrl;
} }

View file

@ -105,6 +105,17 @@ public class ApplicationAdapter implements ApplicationModel {
updateApplication(); updateApplication();
} }
@Override
public String getBaseUrl() {
return applicationData.getBaseUrl();
}
@Override
public void setBaseUrl(String url) {
applicationData.setBaseUrl(url);
updateApplication();
}
@Override @Override
public RoleAdapter getRole(String name) { public RoleAdapter getRole(String name) {
Role role = SampleModel.getRole(getIdm(), name); Role role = SampleModel.getRole(getIdm(), name);

View file

@ -13,6 +13,7 @@ public class ApplicationData extends AbstractPartition {
private boolean enabled; private boolean enabled;
private boolean surrogateAuthRequired; private boolean surrogateAuthRequired;
private String managementUrl; private String managementUrl;
private String baseUrl;
private User resourceUser; private User resourceUser;
private String[] defaultRoles; private String[] defaultRoles;
@ -58,6 +59,15 @@ public class ApplicationData extends AbstractPartition {
this.surrogateAuthRequired = surrogateAuthRequired; this.surrogateAuthRequired = surrogateAuthRequired;
} }
@AttributeProperty
public String getBaseUrl() {
return baseUrl;
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
@AttributeProperty @AttributeProperty
public String getManagementUrl() { public String getManagementUrl() {
return managementUrl; return managementUrl;

View file

@ -31,6 +31,8 @@ public class ApplicationEntity implements Serializable {
private boolean surrogateAuthRequired; private boolean surrogateAuthRequired;
@AttributeValue @AttributeValue
private String managementUrl; private String managementUrl;
@AttributeValue
private String baseUrl;
@AttributeValue @AttributeValue
private String[] defaultRoles; private String[] defaultRoles;

View file

@ -53,6 +53,7 @@ public interface FormService {
private FormFlows.MessageType messageType; private FormFlows.MessageType messageType;
private MultivaluedMap<String, String> formData; private MultivaluedMap<String, String> formData;
private Map<String, String> queryParams;
private URI baseURI; private URI baseURI;
private List<SocialProvider> socialProviders; private List<SocialProvider> socialProviders;
@ -87,10 +88,11 @@ public interface FormService {
private String contextPath; private String contextPath;
public FormServiceDataBean(RealmModel realm, UserModel userModel, MultivaluedMap<String, String> formData, String message) { public FormServiceDataBean(RealmModel realm, UserModel userModel, MultivaluedMap<String, String> formData, Map<String, String> queryParams, String message) {
this.realm = realm; this.realm = realm;
this.userModel = userModel; this.userModel = userModel;
this.formData = formData; this.formData = formData;
this.queryParams = queryParams;
this.message = message; this.message = message;
socialProviders = new LinkedList<SocialProvider>(); socialProviders = new LinkedList<SocialProvider>();
@ -125,6 +127,16 @@ public interface FormService {
return formData; return formData;
} }
public Map<String, String> getQueryParams() {
return queryParams;
}
public String getQueryParam(String key) {
return queryParams != null ? queryParams.get(key) : null;
}
public void setFormData(MultivaluedMap<String, String> formData) { public void setFormData(MultivaluedMap<String, String> formData) {
this.formData = formData; this.formData = formData;
} }

View file

@ -50,6 +50,7 @@ public class ApplicationManager {
applicationModel.setEnabled(resourceRep.isEnabled()); applicationModel.setEnabled(resourceRep.isEnabled());
applicationModel.setManagementUrl(resourceRep.getAdminUrl()); applicationModel.setManagementUrl(resourceRep.getAdminUrl());
applicationModel.setSurrogateAuthRequired(resourceRep.isSurrogateAuthRequired()); applicationModel.setSurrogateAuthRequired(resourceRep.isSurrogateAuthRequired());
applicationModel.setBaseUrl(resourceRep.getBaseUrl());
applicationModel.updateApplication(); applicationModel.updateApplication();
UserModel resourceUser = applicationModel.getApplicationUser(); UserModel resourceUser = applicationModel.getApplicationUser();
@ -128,6 +129,7 @@ public class ApplicationManager {
resource.setName(rep.getName()); resource.setName(rep.getName());
resource.setEnabled(rep.isEnabled()); resource.setEnabled(rep.isEnabled());
resource.setManagementUrl(rep.getAdminUrl()); resource.setManagementUrl(rep.getAdminUrl());
resource.setBaseUrl(rep.getBaseUrl());
resource.setSurrogateAuthRequired(rep.isSurrogateAuthRequired()); resource.setSurrogateAuthRequired(rep.isSurrogateAuthRequired());
resource.updateApplication(); resource.updateApplication();
@ -153,6 +155,7 @@ public class ApplicationManager {
rep.setEnabled(applicationModel.isEnabled()); rep.setEnabled(applicationModel.isEnabled());
rep.setAdminUrl(applicationModel.getManagementUrl()); rep.setAdminUrl(applicationModel.getManagementUrl());
rep.setSurrogateAuthRequired(applicationModel.isSurrogateAuthRequired()); rep.setSurrogateAuthRequired(applicationModel.isSurrogateAuthRequired());
rep.setBaseUrl(applicationModel.getBaseUrl());
Set<String> redirectUris = applicationModel.getApplicationUser().getRedirectUris(); Set<String> redirectUris = applicationModel.getApplicationUser().getRedirectUris();
if (redirectUris != null) { if (redirectUris != null) {

View file

@ -46,6 +46,7 @@ import javax.ws.rs.*;
import javax.ws.rs.core.*; import javax.ws.rs.core.*;
import javax.ws.rs.ext.Providers; import javax.ws.rs.ext.Providers;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException;
import java.util.List; import java.util.List;
/** /**
@ -89,7 +90,15 @@ public class AccountService {
if (!hasAccess(auth)) { if (!hasAccess(auth)) {
return noAccess(); 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 { } else {
return login(path); return login(path);
} }
@ -321,11 +330,8 @@ public class AccountService {
throw new BadRequestException(); throw new BadRequestException();
} }
UriBuilder redirectBuilder = Urls.accountBase(uriInfo.getBaseUri()); URI accountUri = Urls.accountBase(uriInfo.getBaseUri()).path("/").build(realm.getId());
if (path != null) { URI redirectUri = path != null ? accountUri.resolve(path) : accountUri;
redirectBuilder.path(path);
}
URI redirectUri = redirectBuilder.build(realm.getId());
NewCookie cookie = authManager.createAccountIdentityCookie(realm, accessCode.getUser(), client, Urls.accountBase(uriInfo.getBaseUri()).build(realm.getId())); NewCookie cookie = authManager.createAccountIdentityCookie(realm, accessCode.getUser(), client, Urls.accountBase(uriInfo.getBaseUri()).build(realm.getId()));
return Response.status(302).cookie(cookie).location(redirectUri).build(); 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()); 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()); oauth.setStateCookiePath(accountUri.getPath());
return oauth.redirect(uriInfo, accountUri.toString(), path); return oauth.redirect(uriInfo, accountUri.toString(), path);
} }
@ -393,4 +404,23 @@ public class AccountService {
return application.hasRole(user, role); 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;
}
} }

View file

@ -39,18 +39,16 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.UriInfo;
import java.net.URI; import java.net.URI;
import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/ */
public class FormFlows { 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"; public static final String CODE = "code";
// TODO refactor/rename "error" to "message" everywhere where it makes sense // TODO refactor/rename "error" to "message" everywhere where it makes sense
@ -61,6 +59,8 @@ public class FormFlows {
private MultivaluedMap<String, String> formData; private MultivaluedMap<String, String> formData;
private Map<String, String> queryParams;
private RealmModel realm; private RealmModel realm;
private HttpRequest request; private HttpRequest request;
@ -118,6 +118,12 @@ public class FormFlows {
uriBuilder.queryParam(CODE, accessCode.getCode()); uriBuilder.queryParam(CODE, accessCode.getCode());
} }
if (queryParams != null) {
for (Map.Entry<String, String> q : queryParams.entrySet()) {
uriBuilder.replaceQueryParam(q.getKey(), q.getValue());
}
}
URI baseURI = uriBuilder.build(); URI baseURI = uriBuilder.build();
formDataBean.setBaseURI(baseURI); formDataBean.setBaseURI(baseURI);
@ -140,7 +146,7 @@ public class FormFlows {
public Response forwardToForm(String template) { 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); formDataBean.setMessageType(messageType);
return forwardToForm(template, formDataBean); return forwardToForm(template, formDataBean);
@ -192,7 +198,7 @@ public class FormFlows {
public Response forwardToOAuthGrant(){ 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<RoleModel>) request.getAttribute("realmRolesRequested")); formDataBean.setOAuthRealmRolesRequested((List<RoleModel>) request.getAttribute("realmRolesRequested"));
formDataBean.setOAuthResourceRolesRequested((MultivaluedMap<String, RoleModel>) request.getAttribute("resourceRolesRequested")); formDataBean.setOAuthResourceRolesRequested((MultivaluedMap<String, RoleModel>) request.getAttribute("resourceRolesRequested"));
@ -208,6 +214,14 @@ public class FormFlows {
return this; return this;
} }
public FormFlows setQueryParam(String key, String value) {
if (queryParams == null) {
queryParams = new HashMap<String, String>();
}
queryParams.put(key, value);
return this;
}
public FormFlows setError(String error) { public FormFlows setError(String error) {
this.error = error; this.error = error;
return this; return this;

View file

@ -40,6 +40,7 @@ public class ApplicationModelTest extends AbstractKeycloakServerTest {
realm = manager.createRealm("original"); realm = manager.createRealm("original");
application = realm.addApplication("application"); application = realm.addApplication("application");
application.setBaseUrl("http://base");
application.setManagementUrl("http://management"); application.setManagementUrl("http://management");
application.setName("app-name"); application.setName("app-name");
application.addRole("role-1"); application.addRole("role-1");
@ -82,6 +83,7 @@ public class ApplicationModelTest extends AbstractKeycloakServerTest {
public static void assertEquals(ApplicationModel expected, ApplicationModel actual) { public static void assertEquals(ApplicationModel expected, ApplicationModel actual) {
Assert.assertEquals(expected.getName(), actual.getName()); Assert.assertEquals(expected.getName(), actual.getName());
Assert.assertEquals(expected.getBaseUrl(), actual.getBaseUrl());
Assert.assertEquals(expected.getManagementUrl(), actual.getManagementUrl()); Assert.assertEquals(expected.getManagementUrl(), actual.getManagementUrl());
Assert.assertEquals(expected.getDefaultRoles(), actual.getDefaultRoles()); Assert.assertEquals(expected.getDefaultRoles(), actual.getDefaultRoles());

View file

@ -39,23 +39,11 @@ import java.util.List;
*/ */
public class ApplicationServlet extends HttpServlet { public class ApplicationServlet extends HttpServlet {
private static final String LINK = "<a href=\"%s\" id=\"%s\">%s</a>";
@Override @Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String title = ""; String title = "";
String body = "";
StringBuffer sb = req.getRequestURL();
sb.append("?");
sb.append(req.getQueryString());
List<NameValuePair> query = null;
try {
query = URLEncodedUtils.parse(new URI(sb.toString()), "UTF-8");
} catch (URISyntaxException e) {
throw new ServletException(e);
}
if (req.getRequestURI().endsWith("auth")) { if (req.getRequestURI().endsWith("auth")) {
title = "AUTH_RESPONSE"; title = "AUTH_RESPONSE";
} else if (req.getRequestURI().endsWith("logout")) { } else if (req.getRequestURI().endsWith("logout")) {
@ -65,7 +53,11 @@ public class ApplicationServlet extends HttpServlet {
} }
PrintWriter pw = resp.getWriter(); PrintWriter pw = resp.getWriter();
pw.printf("<html><head><title>%s</title></head><body>%s</body>", title, body); pw.printf("<html><head><title>%s</title></head><body>", title);
pw.printf(LINK, "http://localhost:8081/auth-server/rest/realms/test/account", "account", "account");
pw.print("</body></html>");
pw.flush(); pw.flush();
} }

View file

@ -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 @Test
public void changePassword() { public void changePassword() {
changePasswordPage.open(); changePasswordPage.open();

View file

@ -30,7 +30,7 @@ import org.openqa.selenium.support.FindBy;
*/ */
public class AccountUpdateProfilePage extends AbstractAccountPage { 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") @FindBy(id = "firstName")
private WebElement firstNameInput; private WebElement firstNameInput;
@ -41,6 +41,10 @@ public class AccountUpdateProfilePage extends AbstractAccountPage {
@FindBy(id = "email") @FindBy(id = "email")
private WebElement emailInput; private WebElement emailInput;
@FindBy(linkText = "Back to application")
private WebElement backToApplicationLink;
@FindBy(css = "button[type=\"submit\"]") @FindBy(css = "button[type=\"submit\"]")
private WebElement submitButton; private WebElement submitButton;
@ -78,6 +82,10 @@ public class AccountUpdateProfilePage extends AbstractAccountPage {
driver.navigate().to(PATH); driver.navigate().to(PATH);
} }
public void backToApplication() {
backToApplicationLink.click();
}
public boolean isSuccess(){ public boolean isSuccess(){
return feedbackMessage != null && "Success!".equals(feedbackMessage.getText()); return feedbackMessage != null && "Success!".equals(feedbackMessage.getText());
} }

View file

@ -22,6 +22,9 @@
package org.keycloak.testsuite.pages; package org.keycloak.testsuite.pages;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/ */
@ -29,6 +32,9 @@ public class AppPage extends AbstractPage {
private String baseUrl = "http://localhost:8081/app"; private String baseUrl = "http://localhost:8081/app";
@FindBy(id = "account")
private WebElement accountLink;
@Override @Override
public void open() { public void open() {
driver.navigate().to(baseUrl); driver.navigate().to(baseUrl);
@ -43,6 +49,10 @@ public class AppPage extends AbstractPage {
return RequestType.valueOf(driver.getTitle()); return RequestType.valueOf(driver.getTitle());
} }
public void openAccount() {
accountLink.click();
}
public enum RequestType { public enum RequestType {
AUTH_RESPONSE, LOGOUT_REQUEST, APP_REQUEST AUTH_RESPONSE, LOGOUT_REQUEST, APP_REQUEST
} }

View file

@ -72,6 +72,7 @@
{ {
"name": "test-app", "name": "test-app",
"enabled": true, "enabled": true,
"baseUrl": "http://localhost:8081/app",
"adminUrl": "http://localhost:8081/app/logout", "adminUrl": "http://localhost:8081/app/logout",
"credentials": [ "credentials": [
{ {