Merge pull request #215 from stianst/master
KEYCLOAK-297 Fixed invalid state param when loggin to admin acct mngmt
This commit is contained in:
commit
50d0f2f3b2
8 changed files with 145 additions and 31 deletions
|
@ -5,6 +5,7 @@ import org.keycloak.account.Account;
|
||||||
import org.keycloak.account.AccountPages;
|
import org.keycloak.account.AccountPages;
|
||||||
import org.keycloak.account.freemarker.model.AccountBean;
|
import org.keycloak.account.freemarker.model.AccountBean;
|
||||||
import org.keycloak.account.freemarker.model.MessageBean;
|
import org.keycloak.account.freemarker.model.MessageBean;
|
||||||
|
import org.keycloak.account.freemarker.model.ReferrerBean;
|
||||||
import org.keycloak.account.freemarker.model.TotpBean;
|
import org.keycloak.account.freemarker.model.TotpBean;
|
||||||
import org.keycloak.account.freemarker.model.UrlBean;
|
import org.keycloak.account.freemarker.model.UrlBean;
|
||||||
import org.keycloak.freemarker.FreeMarkerException;
|
import org.keycloak.freemarker.FreeMarkerException;
|
||||||
|
@ -80,7 +81,12 @@ public class FreeMarkerAccount implements Account {
|
||||||
attributes.put("message", new MessageBean(messages.containsKey(message) ? messages.getProperty(message) : message, messageType));
|
attributes.put("message", new MessageBean(messages.containsKey(message) ? messages.getProperty(message) : message, messageType));
|
||||||
}
|
}
|
||||||
|
|
||||||
attributes.put("url", new UrlBean(realm, theme, baseUri, getReferrerUri()));
|
ApplicationModel referrerApp = getReferrer();
|
||||||
|
if (referrerApp != null) {
|
||||||
|
attributes.put("referrer", new ReferrerBean(referrerApp));
|
||||||
|
}
|
||||||
|
|
||||||
|
attributes.put("url", new UrlBean(realm, theme, baseUri));
|
||||||
|
|
||||||
switch (page) {
|
switch (page) {
|
||||||
case ACCOUNT:
|
case ACCOUNT:
|
||||||
|
@ -100,11 +106,11 @@ public class FreeMarkerAccount implements Account {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getReferrerUri() {
|
private ApplicationModel getReferrer() {
|
||||||
if (referrer != null) {
|
if (referrer != null) {
|
||||||
ApplicationModel app = realm.getApplicationByName(referrer);
|
ApplicationModel app = realm.getApplicationByName(referrer);
|
||||||
if (app != null) {
|
if (app != null) {
|
||||||
return app.getBaseUrl();
|
return app;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
package org.keycloak.account.freemarker.model;
|
||||||
|
|
||||||
|
import org.keycloak.models.ApplicationModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class ReferrerBean {
|
||||||
|
|
||||||
|
private ApplicationModel referrer;
|
||||||
|
|
||||||
|
public ReferrerBean(ApplicationModel referrer) {
|
||||||
|
this.referrer = referrer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return referrer.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBaseUrl() {
|
||||||
|
return referrer.getBaseUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -14,13 +14,11 @@ public class UrlBean {
|
||||||
private String realm;
|
private String realm;
|
||||||
private Theme theme;
|
private Theme theme;
|
||||||
private URI baseURI;
|
private URI baseURI;
|
||||||
private String referrerURI;
|
|
||||||
|
|
||||||
public UrlBean(RealmModel realm, Theme theme, URI baseURI, String referrerURI) {
|
public UrlBean(RealmModel realm, Theme theme, URI baseURI) {
|
||||||
this.realm = realm.getName();
|
this.realm = realm.getName();
|
||||||
this.theme = theme;
|
this.theme = theme;
|
||||||
this.baseURI = baseURI;
|
this.baseURI = baseURI;
|
||||||
this.referrerURI = referrerURI;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAccessUrl() {
|
public String getAccessUrl() {
|
||||||
|
@ -51,10 +49,6 @@ public class UrlBean {
|
||||||
return Urls.accountLogout(baseURI, realm).toString();
|
return Urls.accountLogout(baseURI, realm).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getReferrerURI() {
|
|
||||||
return referrerURI;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getResourcesPath() {
|
public String getResourcesPath() {
|
||||||
URI uri = Urls.themeRoot(baseURI);
|
URI uri = Urls.themeRoot(baseURI);
|
||||||
return uri.getPath() + "/" + theme.getType().toString().toLowerCase() +"/" + theme.getName();
|
return uri.getPath() + "/" + theme.getType().toString().toLowerCase() +"/" + theme.getName();
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
|
|
||||||
<div class="navbar-collapse">
|
<div class="navbar-collapse">
|
||||||
<ul class="nav navbar-nav navbar-utility">
|
<ul class="nav navbar-nav navbar-utility">
|
||||||
<#if url.referrerURI??><li><a href="${url.referrerURI}">Back to application</a></li></#if>
|
<#if referrer?has_content><li><a href="${referrer.baseUrl}">Back to ${referrer.name}</a></li></#if>
|
||||||
<li><a href="${url.logoutUrl}">Sign Out</a></li>
|
<li><a href="${url.logoutUrl}">Sign Out</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -82,14 +82,7 @@ public class JaxrsOAuthClient extends AbstractOAuthClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public Response redirect(UriInfo uriInfo, String redirectUri) {
|
public Response redirect(UriInfo uriInfo, String redirectUri) {
|
||||||
return redirect(uriInfo, redirectUri, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Response redirect(UriInfo uriInfo, String redirectUri, String path) {
|
|
||||||
String state = getStateCode();
|
String state = getStateCode();
|
||||||
if (path != null) {
|
|
||||||
state += "#" + path;
|
|
||||||
}
|
|
||||||
|
|
||||||
UriBuilder uriBuilder = UriBuilder.fromUri(authUrl)
|
UriBuilder uriBuilder = UriBuilder.fromUri(authUrl)
|
||||||
.queryParam("client_id", clientId)
|
.queryParam("client_id", clientId)
|
||||||
|
@ -98,6 +91,7 @@ public class JaxrsOAuthClient extends AbstractOAuthClient {
|
||||||
if (scope != null) {
|
if (scope != null) {
|
||||||
uriBuilder.queryParam("scope", scope);
|
uriBuilder.queryParam("scope", scope);
|
||||||
}
|
}
|
||||||
|
|
||||||
URI url = uriBuilder.build();
|
URI url = uriBuilder.build();
|
||||||
|
|
||||||
NewCookie cookie = new NewCookie(getStateCookieName(), state, getStateCookiePath(uriInfo), null, null, -1, isSecure, true);
|
NewCookie cookie = new NewCookie(getStateCookieName(), state, getStateCookiePath(uriInfo), null, null, -1, isSecure, true);
|
||||||
|
@ -130,7 +124,7 @@ public class JaxrsOAuthClient extends AbstractOAuthClient {
|
||||||
return uriInfo.getQueryParameters().getFirst("code");
|
return uriInfo.getQueryParameters().getFirst("code");
|
||||||
}
|
}
|
||||||
|
|
||||||
public String checkStateCookie(UriInfo uriInfo, HttpHeaders headers) {
|
public void checkStateCookie(UriInfo uriInfo, HttpHeaders headers) {
|
||||||
Cookie stateCookie = headers.getCookies().get(stateCookieName);
|
Cookie stateCookie = headers.getCookies().get(stateCookieName);
|
||||||
if (stateCookie == null) throw new BadRequestException("state cookie not set");
|
if (stateCookie == null) throw new BadRequestException("state cookie not set");
|
||||||
String state = uriInfo.getQueryParameters().getFirst("state");
|
String state = uriInfo.getQueryParameters().getFirst("state");
|
||||||
|
@ -138,10 +132,5 @@ public class JaxrsOAuthClient extends AbstractOAuthClient {
|
||||||
if (!state.equals(stateCookie.getValue())) {
|
if (!state.equals(stateCookie.getValue())) {
|
||||||
throw new BadRequestException("state parameter invalid");
|
throw new BadRequestException("state parameter invalid");
|
||||||
}
|
}
|
||||||
if (state.indexOf('#') != -1) {
|
|
||||||
return state.substring(state.indexOf('#') + 1);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -259,6 +259,8 @@ public class AccountService {
|
||||||
public Response loginRedirect(@QueryParam("code") String code,
|
public Response loginRedirect(@QueryParam("code") String code,
|
||||||
@QueryParam("state") String state,
|
@QueryParam("state") String state,
|
||||||
@QueryParam("error") String error,
|
@QueryParam("error") String error,
|
||||||
|
@QueryParam("path") String path,
|
||||||
|
@QueryParam("referrer") String referrer,
|
||||||
@Context HttpHeaders headers) {
|
@Context HttpHeaders headers) {
|
||||||
try {
|
try {
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
|
@ -282,7 +284,6 @@ public class AccountService {
|
||||||
logger.debug("state not specified");
|
logger.debug("state not specified");
|
||||||
throw new BadRequestException();
|
throw new BadRequestException();
|
||||||
}
|
}
|
||||||
String path = new JaxrsOAuthClient().checkStateCookie(uriInfo, headers);
|
|
||||||
|
|
||||||
JWSInput input = new JWSInput(code);
|
JWSInput input = new JWSInput(code);
|
||||||
boolean verifiedCode = false;
|
boolean verifiedCode = false;
|
||||||
|
@ -321,6 +322,9 @@ public class AccountService {
|
||||||
|
|
||||||
URI accountUri = Urls.accountBase(uriInfo.getBaseUri()).path("/").build(realm.getName());
|
URI accountUri = Urls.accountBase(uriInfo.getBaseUri()).path("/").build(realm.getName());
|
||||||
URI redirectUri = path != null ? accountUri.resolve(path) : accountUri;
|
URI redirectUri = path != null ? accountUri.resolve(path) : accountUri;
|
||||||
|
if (referrer != null) {
|
||||||
|
redirectUri = redirectUri.resolve("?referrer=" + referrer);
|
||||||
|
}
|
||||||
|
|
||||||
NewCookie cookie = authManager.createAccountIdentityCookie(realm, accessCode.getUser(), client, Urls.accountBase(uriInfo.getBaseUri()).build(realm.getName()));
|
NewCookie cookie = authManager.createAccountIdentityCookie(realm, accessCode.getUser(), client, Urls.accountBase(uriInfo.getBaseUri()).build(realm.getName()));
|
||||||
return Response.status(302).cookie(cookie).location(redirectUri).build();
|
return Response.status(302).cookie(cookie).location(redirectUri).build();
|
||||||
|
@ -346,15 +350,22 @@ public class AccountService {
|
||||||
|
|
||||||
oauth.setClientId(Constants.ACCOUNT_APPLICATION);
|
oauth.setClientId(Constants.ACCOUNT_APPLICATION);
|
||||||
|
|
||||||
URI accountUri = Urls.accountPageBuilder(uriInfo.getBaseUri()).path(AccountService.class, "loginRedirect").build(realm.getName());
|
UriBuilder uriBuilder = Urls.accountPageBuilder(uriInfo.getBaseUri()).path(AccountService.class, "loginRedirect");
|
||||||
|
|
||||||
|
if (path != null) {
|
||||||
|
uriBuilder.queryParam("path", path);
|
||||||
|
}
|
||||||
|
|
||||||
String referrer = getReferrer();
|
String referrer = getReferrer();
|
||||||
if (referrer != null) {
|
if (referrer != null) {
|
||||||
path = (path != null ? path : "") + "?referrer=" + referrer;
|
uriBuilder.queryParam("referrer", referrer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
URI accountUri = uriBuilder.build(realm.getName());
|
||||||
|
|
||||||
|
|
||||||
oauth.setStateCookiePath(accountUri.getRawPath());
|
oauth.setStateCookiePath(accountUri.getRawPath());
|
||||||
return oauth.redirect(uriInfo, accountUri.toString(), path);
|
return oauth.redirect(uriInfo, accountUri.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private AuthenticationManager.Auth getAuth(boolean error) {
|
private AuthenticationManager.Auth getAuth(boolean error) {
|
||||||
|
|
|
@ -235,10 +235,15 @@ public class AdminService {
|
||||||
logger.debug("authUrl: {0}", authUrl);
|
logger.debug("authUrl: {0}", authUrl);
|
||||||
oauth.setAuthUrl(authUrl);
|
oauth.setAuthUrl(authUrl);
|
||||||
oauth.setClientId(Constants.ADMIN_CONSOLE_APPLICATION);
|
oauth.setClientId(Constants.ADMIN_CONSOLE_APPLICATION);
|
||||||
URI redirectUri = uriInfo.getBaseUriBuilder().path(AdminService.class).path(AdminService.class, "loginRedirect").build();
|
|
||||||
|
UriBuilder redirectBuilder = uriInfo.getBaseUriBuilder().path(AdminService.class).path(AdminService.class, "loginRedirect");
|
||||||
|
if (path != null) {
|
||||||
|
redirectBuilder.queryParam("path", path);
|
||||||
|
}
|
||||||
|
URI redirectUri = redirectBuilder.build();
|
||||||
logger.debug("redirectUri: {0}", redirectUri.toString());
|
logger.debug("redirectUri: {0}", redirectUri.toString());
|
||||||
oauth.setStateCookiePath(redirectUri.getRawPath());
|
oauth.setStateCookiePath(redirectUri.getRawPath());
|
||||||
return oauth.redirect(uriInfo, redirectUri.toString(), path);
|
return oauth.redirect(uriInfo, redirectUri.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("login-error")
|
@Path("login-error")
|
||||||
|
@ -263,6 +268,7 @@ public class AdminService {
|
||||||
public Response loginRedirect(@QueryParam("code") String code,
|
public Response loginRedirect(@QueryParam("code") String code,
|
||||||
@QueryParam("state") String state,
|
@QueryParam("state") String state,
|
||||||
@QueryParam("error") String error,
|
@QueryParam("error") String error,
|
||||||
|
@QueryParam("path") String path,
|
||||||
@Context HttpHeaders headers
|
@Context HttpHeaders headers
|
||||||
|
|
||||||
) {
|
) {
|
||||||
|
@ -293,7 +299,7 @@ public class AdminService {
|
||||||
logger.debug("state not specified");
|
logger.debug("state not specified");
|
||||||
return redirectOnLoginError("invalid login data");
|
return redirectOnLoginError("invalid login data");
|
||||||
}
|
}
|
||||||
String path = new JaxrsOAuthClient().checkStateCookie(uriInfo, headers);
|
new JaxrsOAuthClient().checkStateCookie(uriInfo, headers);
|
||||||
|
|
||||||
JWSInput input = new JWSInput(code);
|
JWSInput input = new JWSInput(code);
|
||||||
boolean verifiedCode = false;
|
boolean verifiedCode = false;
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
/*
|
||||||
|
* 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.forms;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.ClassRule;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.testsuite.OAuthClient;
|
||||||
|
import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
|
||||||
|
import org.keycloak.testsuite.pages.AppPage;
|
||||||
|
import org.keycloak.testsuite.pages.AppPage.RequestType;
|
||||||
|
import org.keycloak.testsuite.pages.LoginPage;
|
||||||
|
import org.keycloak.testsuite.rule.KeycloakRule;
|
||||||
|
import org.keycloak.testsuite.rule.WebResource;
|
||||||
|
import org.keycloak.testsuite.rule.WebRule;
|
||||||
|
import org.openqa.selenium.WebDriver;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class SSOTest {
|
||||||
|
|
||||||
|
@ClassRule
|
||||||
|
public static KeycloakRule keycloakRule = new KeycloakRule();
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public WebRule webRule = new WebRule(this);
|
||||||
|
|
||||||
|
@WebResource
|
||||||
|
protected OAuthClient oauth;
|
||||||
|
|
||||||
|
|
||||||
|
@WebResource
|
||||||
|
protected WebDriver driver;
|
||||||
|
|
||||||
|
@WebResource
|
||||||
|
protected AppPage appPage;
|
||||||
|
|
||||||
|
@WebResource
|
||||||
|
protected LoginPage loginPage;
|
||||||
|
|
||||||
|
@WebResource
|
||||||
|
protected AccountUpdateProfilePage profilePage;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void loginSuccess() {
|
||||||
|
loginPage.open();
|
||||||
|
loginPage.login("test-user@localhost", "password");
|
||||||
|
|
||||||
|
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
|
Assert.assertNotNull(oauth.getCurrentQuery().get("code"));
|
||||||
|
|
||||||
|
appPage.open();
|
||||||
|
|
||||||
|
oauth.openLoginForm();
|
||||||
|
|
||||||
|
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
|
|
||||||
|
profilePage.open();
|
||||||
|
|
||||||
|
Assert.assertTrue(profilePage.isCurrent());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue