Skip to content

Commit

Permalink
KEYCLOAK-2828 Refactor contribution and add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
stianst committed Apr 19, 2016
1 parent cd1094c commit 5606160
Show file tree
Hide file tree
Showing 9 changed files with 221 additions and 31 deletions.
Expand Up @@ -17,7 +17,6 @@

package org.keycloak.protocol.oidc.endpoints;

import org.jboss.logging.Logger;
import org.jboss.resteasy.spi.NotFoundException;
import org.keycloak.Config;
import org.keycloak.models.ClientModel;
Expand All @@ -26,10 +25,7 @@
import org.keycloak.protocol.oidc.utils.RedirectUtils;
import org.keycloak.common.util.StreamUtil;
import org.keycloak.common.util.UriUtils;
import org.keycloak.saml.common.util.StringUtil;
import org.keycloak.services.validation.Validation;
import org.keycloak.theme.Theme;
import org.keycloak.theme.ThemeProvider;
import org.keycloak.services.util.P3PHelper;

import javax.ws.rs.GET;
import javax.ws.rs.Produces;
Expand All @@ -47,13 +43,12 @@
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class LoginStatusIframeEndpoint {
private static final Logger logger = Logger.getLogger(LoginStatusIframeEndpoint.class);

@Context
private UriInfo uriInfo;

@Context
protected KeycloakSession session;
private KeycloakSession session;

private RealmModel realm;

Expand Down Expand Up @@ -105,32 +100,17 @@ public Response getLoginStatusIframe(@QueryParam("client_id") String client_id,
throw new WebApplicationException(Response.Status.BAD_REQUEST);
}

ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
Theme theme;
try {
theme = themeProvider.getTheme(realm.getLoginTheme(), Theme.Type.LOGIN);
} catch (IOException e) {
logger.error("Failed to create theme", e);
return Response.serverError().build();
}

try {
String file = StreamUtil.readString(is);
file = file.replace("ORIGIN", origin);

Response.ResponseBuilder response = Response.ok(file);

String p3pValue = theme.getProperties().getProperty("sessionIframeP3P");
if (!Validation.isBlank(p3pValue)) {
// This header is required by IE, see KEYCLOAK-2828 for details.
response.header("P3P", p3pValue);
}
P3PHelper.addP3PHeader(session);

CacheControl cacheControl = new CacheControl();
cacheControl.setNoTransform(false);
cacheControl.setMaxAge(Config.scope("theme").getInt("staticMaxAge", -1));

return response.cacheControl(cacheControl).build();
return Response.ok(file).cacheControl(cacheControl).build();
} catch (IOException e) {
throw new WebApplicationException(e, Response.Status.BAD_REQUEST);
}
Expand Down
Expand Up @@ -36,7 +36,7 @@ public AuthResult authenticateIdentityCookie(KeycloakSession session, RealmModel
AuthResult authResult = super.authenticateIdentityCookie(session, realm);
if (authResult == null) return null;
// refresh the cookies!
createLoginCookie(realm, authResult.getUser(), authResult.getSession(), session.getContext().getUri(), session.getContext().getConnection());
createLoginCookie(session, realm, authResult.getUser(), authResult.getSession(), session.getContext().getUri(), session.getContext().getConnection());
if (authResult.getSession().isRememberMe()) createRememberMeCookie(realm, authResult.getUser().getUsername(), session.getContext().getUri(), session.getContext().getConnection());
return authResult;
}
Expand Down
Expand Up @@ -45,6 +45,7 @@
import org.keycloak.services.resources.RealmsResource;
import org.keycloak.services.util.CookieHelper;
import org.keycloak.common.util.Time;
import org.keycloak.services.util.P3PHelper;

import javax.ws.rs.core.*;
import java.net.URI;
Expand Down Expand Up @@ -259,7 +260,7 @@ public static AccessToken createIdentityToken(RealmModel realm, UserModel user,
return token;
}

public static void createLoginCookie(RealmModel realm, UserModel user, UserSessionModel session, UriInfo uriInfo, ClientConnection connection) {
public static void createLoginCookie(KeycloakSession keycloakSession, RealmModel realm, UserModel user, UserSessionModel session, UriInfo uriInfo, ClientConnection connection) {
String cookiePath = getIdentityCookiePath(realm, uriInfo);
String issuer = Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName());
AccessToken identityToken = createIdentityToken(realm, user, session, issuer);
Expand All @@ -280,7 +281,7 @@ public static void createLoginCookie(RealmModel realm, UserModel user, UserSessi
// THIS SHOULD NOT BE A HTTPONLY COOKIE! It is used for OpenID Connect Iframe Session support!
// Max age should be set to the max lifespan of the session as it's used to invalidate old-sessions on re-login
CookieHelper.addCookie(KEYCLOAK_SESSION_COOKIE, sessionCookieValue, cookiePath, null, null, realm.getSsoSessionMaxLifespan(), secureOnly, false);

P3PHelper.addP3PHeader(keycloakSession);
}

public static void createRememberMeCookie(RealmModel realm, String username, UriInfo uriInfo, ClientConnection connection) {
Expand Down Expand Up @@ -399,7 +400,7 @@ public static Response redirectAfterSuccessfulFlow(KeycloakSession session, Real
session.getContext().resolveLocale(userSession.getUser());

// refresh the cookies!
createLoginCookie(realm, userSession.getUser(), userSession, uriInfo, clientConnection);
createLoginCookie(session, realm, userSession.getUser(), userSession, uriInfo, clientConnection);
if (userSession.getState() != UserSessionModel.State.LOGGED_IN) userSession.setState(UserSessionModel.State.LOGGED_IN);
if (userSession.isRememberMe()) createRememberMeCookie(realm, userSession.getUser().getUsername(), uriInfo, clientConnection);
return protocol.authenticated(userSession, new ClientSessionCode(realm, clientSession));
Expand Down
Expand Up @@ -330,7 +330,7 @@ public Map<String, Object> impersonate(final @PathParam("id") String id) {
EventBuilder event = new EventBuilder(realm, session, clientConnection);

UserSessionModel userSession = session.sessions().createUserSession(realm, user, user.getUsername(), clientConnection.getRemoteAddr(), "impersonate", false, null, null);
AuthenticationManager.createLoginCookie(realm, userSession.getUser(), userSession, uriInfo, clientConnection);
AuthenticationManager.createLoginCookie(session, realm, userSession.getUser(), userSession, uriInfo, clientConnection);
URI redirect = AccountService.accountServiceApplicationPage(uriInfo).build(realm.getName());
Map<String, Object> result = new HashMap<>();
result.put("sameRealm", sameRealm);
Expand Down
Expand Up @@ -16,6 +16,7 @@
*/
package org.keycloak.services.util;

import org.keycloak.models.KeycloakContext;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
Expand Down Expand Up @@ -44,6 +45,25 @@ public static Locale getLocale(KeycloakSession session, RealmModel realm, UserMo
}
}

public static Locale getLocaleFromCookie(KeycloakSession session) {
KeycloakContext ctx = session.getContext();

if (ctx.getRequestHeaders() != null && ctx.getRequestHeaders().getCookies().containsKey(LOCALE_COOKIE)) {
String localeString = ctx.getRequestHeaders().getCookies().get(LOCALE_COOKIE).getValue();
Locale locale = findLocale(ctx.getRealm().getSupportedLocales(), localeString);
if (locale != null) {
return locale;
}
}

String locale = ctx.getRealm().getDefaultLocale();
if (locale != null) {
return Locale.forLanguageTag(locale);
} else {
return Locale.ENGLISH;
}
}

private static Locale getUserLocale(KeycloakSession session, RealmModel realm, UserModel user) {
UriInfo uriInfo = session.getContext().getUri();
HttpHeaders httpHeaders = session.getContext().getRequestHeaders();
Expand Down
58 changes: 58 additions & 0 deletions services/src/main/java/org/keycloak/services/util/P3PHelper.java
@@ -0,0 +1,58 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.keycloak.services.util;

import org.jboss.logging.Logger;
import org.jboss.resteasy.spi.HttpResponse;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.services.validation.Validation;
import org.keycloak.theme.Theme;
import org.keycloak.theme.ThemeProvider;

import java.io.IOException;
import java.util.Locale;

/**
* IE requires P3P header to allow loading cookies from iframes when domain differs from main page (see KEYCLOAK-2828 for more details)
*
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class P3PHelper {

private static final Logger logger = Logger.getLogger(P3PHelper.class);

public static void addP3PHeader(KeycloakSession session) {
try {
ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
Theme theme = themeProvider.getTheme(session.getContext().getRealm().getLoginTheme(), Theme.Type.LOGIN);

Locale locale = LocaleHelper.getLocaleFromCookie(session);
String p3pValue = theme.getMessages(locale).getProperty("p3pPolicy");

if (!Validation.isBlank(p3pValue)) {
HttpResponse response = ResteasyProviderFactory.getContextData(HttpResponse.class);
response.getOutputHeaders().putSingle("P3P", p3pValue);
}
} catch (IOException e) {
logger.error("Failed to set P3P header", e);
return;
}
}

}
@@ -0,0 +1,131 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.keycloak.testsuite.oauth;

import org.apache.commons.io.IOUtils;
import org.apache.http.Header;
import org.apache.http.NameValuePair;
import org.apache.http.client.CookieStore;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.junit.Test;
import org.keycloak.models.Constants;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest;

import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static org.junit.Assert.*;

/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class LoginStatusIframeEndpointTest extends AbstractKeycloakTest {

@Test
public void checkIframeP3PHeader() throws IOException {
CookieStore cookieStore = new BasicCookieStore();

CloseableHttpClient client = HttpClients.custom().setDefaultCookieStore(cookieStore).build();
try {
HttpGet get = new HttpGet(
suiteContext.getAuthServerInfo().getContextRoot() + "/auth/realms/master/protocol/openid-connect/auth?response_type=code&client_id=" + Constants.ADMIN_CONSOLE_CLIENT_ID);

CloseableHttpResponse response = client.execute(get);
String s = IOUtils.toString(response.getEntity().getContent());
response.close();

Matcher matcher = Pattern.compile("action=\"([^\"]*)\"").matcher(s);
matcher.find();

String action = matcher.group(1);

HttpPost post = new HttpPost(action);

List<NameValuePair> params = new LinkedList<>();
params.add(new BasicNameValuePair("username", "admin"));
params.add(new BasicNameValuePair("password", "admin"));

post.setHeader("Content-Type", "application/x-www-form-urlencoded");
post.setEntity(new UrlEncodedFormEntity(params));

response = client.execute(post);

assertEquals("CP=\"This is not a P3P policy!\"", response.getFirstHeader("P3P").getValue());

Header setIdentityCookieHeader = null;
Header setSessionCookieHeader = null;
for (Header h : response.getAllHeaders()) {
if (h.getName().equals("Set-Cookie")) {
if (h.getValue().contains("KEYCLOAK_SESSION")) {
setSessionCookieHeader = h;

} else if (h.getValue().contains("KEYCLOAK_IDENTITY")) {
setIdentityCookieHeader = h;
}
}
}
assertNotNull(setIdentityCookieHeader);
assertTrue(setIdentityCookieHeader.getValue().contains("HttpOnly"));

assertNotNull(setSessionCookieHeader);
assertFalse(setSessionCookieHeader.getValue().contains("HttpOnly"));

response.close();

Cookie sessionCookie = null;
for (Cookie cookie : cookieStore.getCookies()) {
if (cookie.getName().equals("KEYCLOAK_SESSION")) {
sessionCookie = cookie;
break;
}
}
assertNotNull(sessionCookie);

get = new HttpGet(
suiteContext.getAuthServerInfo().getContextRoot() + "/auth/realms/master/protocol/openid-connect/login-status-iframe.html?client_id=" + Constants.ADMIN_CONSOLE_CLIENT_ID + "&origin=" + suiteContext.getAuthServerInfo().getContextRoot());
response = client.execute(get);

assertEquals(200, response.getStatusLine().getStatusCode());
s = IOUtils.toString(response.getEntity().getContent());
assertTrue(s.contains("function getCookie(cname)"));

assertEquals("CP=\"This is not a P3P policy!\"", response.getFirstHeader("P3P").getValue());

response.close();
} finally {
client.close();
}
}

@Override
public void addTestRealms(List<RealmRepresentation> testRealms) {
}

}
Expand Up @@ -223,3 +223,4 @@ clientNotFoundMessage=Client not found.
invalidParameterMessage=Invalid parameter\: {0}
alreadyLoggedIn=You are already logged in.

p3pPolicy=CP="This is not a P3P policy!"
3 changes: 1 addition & 2 deletions themes/src/main/resources/theme/base/login/theme.properties
@@ -1,2 +1 @@
locales=ca,de,en,es,fr,it,pt-BR
sessionIframeP3P=CP="This is not a P3P policy!"
locales=ca,de,en,es,fr,it,pt-BR

0 comments on commit 5606160

Please sign in to comment.