commit
2d36d48356
8 changed files with 223 additions and 5 deletions
|
@ -20,10 +20,12 @@ package org.keycloak.protocol.oidc.endpoints;
|
||||||
import org.jboss.resteasy.spi.NotFoundException;
|
import org.jboss.resteasy.spi.NotFoundException;
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.protocol.oidc.utils.RedirectUtils;
|
import org.keycloak.protocol.oidc.utils.RedirectUtils;
|
||||||
import org.keycloak.common.util.StreamUtil;
|
import org.keycloak.common.util.StreamUtil;
|
||||||
import org.keycloak.common.util.UriUtils;
|
import org.keycloak.common.util.UriUtils;
|
||||||
|
import org.keycloak.services.util.P3PHelper;
|
||||||
|
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
|
@ -45,6 +47,9 @@ public class LoginStatusIframeEndpoint {
|
||||||
@Context
|
@Context
|
||||||
private UriInfo uriInfo;
|
private UriInfo uriInfo;
|
||||||
|
|
||||||
|
@Context
|
||||||
|
private KeycloakSession session;
|
||||||
|
|
||||||
private RealmModel realm;
|
private RealmModel realm;
|
||||||
|
|
||||||
public LoginStatusIframeEndpoint(RealmModel realm) {
|
public LoginStatusIframeEndpoint(RealmModel realm) {
|
||||||
|
@ -99,6 +104,8 @@ public class LoginStatusIframeEndpoint {
|
||||||
String file = StreamUtil.readString(is);
|
String file = StreamUtil.readString(is);
|
||||||
file = file.replace("ORIGIN", origin);
|
file = file.replace("ORIGIN", origin);
|
||||||
|
|
||||||
|
P3PHelper.addP3PHeader(session);
|
||||||
|
|
||||||
CacheControl cacheControl = new CacheControl();
|
CacheControl cacheControl = new CacheControl();
|
||||||
cacheControl.setNoTransform(false);
|
cacheControl.setNoTransform(false);
|
||||||
cacheControl.setMaxAge(Config.scope("theme").getInt("staticMaxAge", -1));
|
cacheControl.setMaxAge(Config.scope("theme").getInt("staticMaxAge", -1));
|
||||||
|
|
|
@ -36,7 +36,7 @@ public class AppAuthManager extends AuthenticationManager {
|
||||||
AuthResult authResult = super.authenticateIdentityCookie(session, realm);
|
AuthResult authResult = super.authenticateIdentityCookie(session, realm);
|
||||||
if (authResult == null) return null;
|
if (authResult == null) return null;
|
||||||
// refresh the cookies!
|
// 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());
|
if (authResult.getSession().isRememberMe()) createRememberMeCookie(realm, authResult.getUser().getUsername(), session.getContext().getUri(), session.getContext().getConnection());
|
||||||
return authResult;
|
return authResult;
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,7 @@ import org.keycloak.services.resources.IdentityBrokerService;
|
||||||
import org.keycloak.services.resources.RealmsResource;
|
import org.keycloak.services.resources.RealmsResource;
|
||||||
import org.keycloak.services.util.CookieHelper;
|
import org.keycloak.services.util.CookieHelper;
|
||||||
import org.keycloak.common.util.Time;
|
import org.keycloak.common.util.Time;
|
||||||
|
import org.keycloak.services.util.P3PHelper;
|
||||||
|
|
||||||
import javax.ws.rs.core.*;
|
import javax.ws.rs.core.*;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
@ -259,7 +260,7 @@ public class AuthenticationManager {
|
||||||
return token;
|
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 cookiePath = getIdentityCookiePath(realm, uriInfo);
|
||||||
String issuer = Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName());
|
String issuer = Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName());
|
||||||
AccessToken identityToken = createIdentityToken(realm, user, session, issuer);
|
AccessToken identityToken = createIdentityToken(realm, user, session, issuer);
|
||||||
|
@ -280,7 +281,7 @@ public class AuthenticationManager {
|
||||||
// THIS SHOULD NOT BE A HTTPONLY COOKIE! It is used for OpenID Connect Iframe Session support!
|
// 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
|
// 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);
|
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) {
|
public static void createRememberMeCookie(RealmModel realm, String username, UriInfo uriInfo, ClientConnection connection) {
|
||||||
|
@ -399,7 +400,7 @@ public class AuthenticationManager {
|
||||||
session.getContext().resolveLocale(userSession.getUser());
|
session.getContext().resolveLocale(userSession.getUser());
|
||||||
|
|
||||||
// refresh the cookies!
|
// 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.getState() != UserSessionModel.State.LOGGED_IN) userSession.setState(UserSessionModel.State.LOGGED_IN);
|
||||||
if (userSession.isRememberMe()) createRememberMeCookie(realm, userSession.getUser().getUsername(), uriInfo, clientConnection);
|
if (userSession.isRememberMe()) createRememberMeCookie(realm, userSession.getUser().getUsername(), uriInfo, clientConnection);
|
||||||
return protocol.authenticated(userSession, new ClientSessionCode(realm, clientSession));
|
return protocol.authenticated(userSession, new ClientSessionCode(realm, clientSession));
|
||||||
|
|
|
@ -330,7 +330,7 @@ public class UsersResource {
|
||||||
EventBuilder event = new EventBuilder(realm, session, clientConnection);
|
EventBuilder event = new EventBuilder(realm, session, clientConnection);
|
||||||
|
|
||||||
UserSessionModel userSession = session.sessions().createUserSession(realm, user, user.getUsername(), clientConnection.getRemoteAddr(), "impersonate", false, null, null);
|
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());
|
URI redirect = AccountService.accountServiceApplicationPage(uriInfo).build(realm.getName());
|
||||||
Map<String, Object> result = new HashMap<>();
|
Map<String, Object> result = new HashMap<>();
|
||||||
result.put("sameRealm", sameRealm);
|
result.put("sameRealm", sameRealm);
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.services.util;
|
package org.keycloak.services.util;
|
||||||
|
|
||||||
|
import org.keycloak.models.KeycloakContext;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
@ -44,6 +45,25 @@ public class LocaleHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
private static Locale getUserLocale(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||||
UriInfo uriInfo = session.getContext().getUri();
|
UriInfo uriInfo = session.getContext().getUri();
|
||||||
HttpHeaders httpHeaders = session.getContext().getRequestHeaders();
|
HttpHeaders httpHeaders = session.getContext().getRequestHeaders();
|
||||||
|
|
|
@ -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) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -223,3 +223,4 @@ clientNotFoundMessage=Client not found.
|
||||||
invalidParameterMessage=Invalid parameter\: {0}
|
invalidParameterMessage=Invalid parameter\: {0}
|
||||||
alreadyLoggedIn=You are already logged in.
|
alreadyLoggedIn=You are already logged in.
|
||||||
|
|
||||||
|
p3pPolicy=CP="This is not a P3P policy!"
|
Loading…
Reference in a new issue