diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-menu.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-menu.html index f527953c52..f34df5f1ec 100755 --- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-menu.html +++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-menu.html @@ -9,7 +9,7 @@
  • Users
  • -
  • Roles +
  • Roles
  • Applications
  • OAuth Clients
  • diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java index 45f1a42a49..2773989d18 100755 --- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java +++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java @@ -173,7 +173,7 @@ public class AuthenticationManager { public AuthResult authenticateIdentityCookie(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers, boolean checkActive) { logger.info("authenticateIdentityCookie"); Cookie cookie = headers.getCookies().get(KEYCLOAK_IDENTITY_COOKIE); - if (cookie == null) { + if (cookie == null || "".equals(cookie.getValue())) { logger.infov("authenticateCookie could not find cookie: {0}", KEYCLOAK_IDENTITY_COOKIE); return null; } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UserFederationResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationResource.java index 554ed7d6f0..f03d4102ec 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/UserFederationResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationResource.java @@ -156,7 +156,7 @@ public class UserFederationResource { @GET @NoCache @Path("instances/{id}") - @Consumes("application/json") + @Produces("application/json") public UserFederationProviderRepresentation getProviderInstance(@PathParam("id") String id) { logger.info("getProvider"); auth.requireView(); diff --git a/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java b/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java index 68bf394117..0b10857f43 100755 --- a/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java +++ b/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java @@ -1,199 +1,203 @@ -/* - * 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.services.resources.flows; - -import org.jboss.logging.Logger; -import org.jboss.resteasy.specimpl.MultivaluedMapImpl; -import org.jboss.resteasy.spi.HttpRequest; -import org.keycloak.ClientConnection; -import org.keycloak.OAuth2Constants; -import org.keycloak.audit.Audit; -import org.keycloak.audit.Details; -import org.keycloak.audit.EventType; -import org.keycloak.models.ApplicationModel; -import org.keycloak.models.ClientModel; -import org.keycloak.models.ClientSessionModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RequiredCredentialModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.UserModel; -import org.keycloak.models.UserModel.RequiredAction; -import org.keycloak.models.UserSessionModel; -import org.keycloak.representations.idm.CredentialRepresentation; -import org.keycloak.services.managers.AccessCode; -import org.keycloak.services.managers.AuthenticationManager; -import org.keycloak.services.managers.TokenManager; - -import javax.ws.rs.core.Cookie; -import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriBuilder; -import javax.ws.rs.core.UriInfo; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; - -/** - * @author Bill Burke - * @author Stian Thorgersen - */ -public class OAuthFlows { - - private static final Logger log = Logger.getLogger(OAuthFlows.class); - - private final KeycloakSession session; - - private final RealmModel realm; - - private final HttpRequest request; - - private final UriInfo uriInfo; - - private ClientConnection clientConnection; - private final AuthenticationManager authManager; - - private final TokenManager tokenManager; - - OAuthFlows(KeycloakSession session, RealmModel realm, HttpRequest request, UriInfo uriInfo, ClientConnection clientConnection, AuthenticationManager authManager, - TokenManager tokenManager) { - this.session = session; - this.realm = realm; - this.request = request; - this.uriInfo = uriInfo; - this.clientConnection = clientConnection; - this.authManager = authManager; - this.tokenManager = tokenManager; - } - - public Response redirectAccessCode(AccessCode accessCode, UserSessionModel userSession, String state, String redirect) { - String code = accessCode.getCode(); - UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.CODE, code); - log.debugv("redirectAccessCode: state: {0}", state); - if (state != null) - redirectUri.queryParam(OAuth2Constants.STATE, state); - Response.ResponseBuilder location = Response.status(302).location(redirectUri.build()); - Cookie remember = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_REMEMBER_ME); - - Cookie sessionCookie = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_SESSION_COOKIE); - if (sessionCookie != null) { - String oldSessionId = sessionCookie.getValue().split("/")[2]; - if (!oldSessionId.equals(userSession.getId())) { - UserSessionModel oldSession = session.sessions().getUserSession(realm, oldSessionId); - if (oldSession != null) { - log.debugv("Removing old user session: session: {0}", oldSessionId); - session.sessions().removeUserSession(realm, oldSession); - } - } - } - - // refresh the cookies! - authManager.createLoginCookie(realm, accessCode.getUser(), userSession, uriInfo, clientConnection); - if (userSession.isRememberMe()) authManager.createRememberMeCookie(realm, uriInfo, clientConnection); - return location.build(); - } - - public Response redirectError(ClientModel client, String error, String state, String redirect) { - UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.ERROR, error); - if (state != null) { - redirectUri.queryParam(OAuth2Constants.STATE, state); - } - return Response.status(302).location(redirectUri.build()).build(); - } - - public Response processAccessCode(String scopeParam, String state, String redirect, ClientModel client, UserModel user, UserSessionModel session, Audit audit) { - isTotpConfigurationRequired(user); - isEmailVerificationRequired(user); - - boolean isResource = client instanceof ApplicationModel; - AccessCode accessCode = tokenManager.createAccessCode(scopeParam, state, redirect, this.session, realm, client, user, session); - - log.debugv("processAccessCode: isResource: {0}", isResource); - log.debugv("processAccessCode: go to oauth page?: {0}", - !isResource); - - audit.detail(Details.CODE_ID, accessCode.getCodeId()); - - Set requiredActions = user.getRequiredActions(); - if (!requiredActions.isEmpty()) { - RequiredAction action = user.getRequiredActions().iterator().next(); - accessCode.setRequiredAction(action); - - if (action.equals(RequiredAction.VERIFY_EMAIL)) { - audit.clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, accessCode.getUser().getEmail()).success(); - } - - return Flows.forms(this.session, realm, client, uriInfo).setAccessCode(accessCode.getCode()).setUser(user) - .createResponse(action); - } - - if (!isResource) { - accessCode.setAction(ClientSessionModel.Action.OAUTH_GRANT); - - List realmRoles = new LinkedList(); - MultivaluedMap resourceRoles = new MultivaluedMapImpl(); - for (RoleModel r : accessCode.getRequestedRoles()) { - if (r.getContainer() instanceof RealmModel) { - realmRoles.add(r); - } else { - resourceRoles.add(((ApplicationModel) r.getContainer()).getName(), r); - } - } - - return Flows.forms(this.session, realm, client, uriInfo) - .setAccessCode(accessCode.getCode()) - .setAccessRequest(realmRoles, resourceRoles) - .setClient(client) - .createOAuthGrant(); - } - - if (redirect != null) { - audit.success(); - - accessCode.setAction(ClientSessionModel.Action.CODE_TO_TOKEN); - return redirectAccessCode(accessCode, session, state, redirect); - } else { - return null; - } - } - - public Response forwardToSecurityFailure(String message) { - return Flows.forms(session, realm, null, uriInfo).setError(message).createErrorPage(); - } - - private void isTotpConfigurationRequired(UserModel user) { - for (RequiredCredentialModel c : realm.getRequiredCredentials()) { - if (c.getType().equals(CredentialRepresentation.TOTP) && !user.isTotp()) { - user.addRequiredAction(RequiredAction.CONFIGURE_TOTP); - log.debug("User is required to configure totp"); - } - } - } - - private void isEmailVerificationRequired(UserModel user) { - if (realm.isVerifyEmail() && !user.isEmailVerified()) { - user.addRequiredAction(RequiredAction.VERIFY_EMAIL); - log.debug("User is required to verify email"); - } - } - -} +/* + * 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.services.resources.flows; + +import org.jboss.logging.Logger; +import org.jboss.resteasy.specimpl.MultivaluedMapImpl; +import org.jboss.resteasy.spi.HttpRequest; +import org.keycloak.ClientConnection; +import org.keycloak.OAuth2Constants; +import org.keycloak.audit.Audit; +import org.keycloak.audit.Details; +import org.keycloak.audit.EventType; +import org.keycloak.models.ApplicationModel; +import org.keycloak.models.ClientModel; +import org.keycloak.models.ClientSessionModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.RequiredCredentialModel; +import org.keycloak.models.RoleModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.UserModel.RequiredAction; +import org.keycloak.models.UserSessionModel; +import org.keycloak.representations.idm.CredentialRepresentation; +import org.keycloak.services.managers.AccessCode; +import org.keycloak.services.managers.AuthenticationManager; +import org.keycloak.services.managers.TokenManager; + +import javax.ws.rs.core.Cookie; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriBuilder; +import javax.ws.rs.core.UriInfo; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +/** + * @author Bill Burke + * @author Stian Thorgersen + */ +public class OAuthFlows { + + private static final Logger log = Logger.getLogger(OAuthFlows.class); + + private final KeycloakSession session; + + private final RealmModel realm; + + private final HttpRequest request; + + private final UriInfo uriInfo; + + private ClientConnection clientConnection; + private final AuthenticationManager authManager; + + private final TokenManager tokenManager; + + OAuthFlows(KeycloakSession session, RealmModel realm, HttpRequest request, UriInfo uriInfo, ClientConnection clientConnection, AuthenticationManager authManager, + TokenManager tokenManager) { + this.session = session; + this.realm = realm; + this.request = request; + this.uriInfo = uriInfo; + this.clientConnection = clientConnection; + this.authManager = authManager; + this.tokenManager = tokenManager; + } + + public Response redirectAccessCode(AccessCode accessCode, UserSessionModel userSession, String state, String redirect) { + String code = accessCode.getCode(); + UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.CODE, code); + log.debugv("redirectAccessCode: state: {0}", state); + if (state != null) + redirectUri.queryParam(OAuth2Constants.STATE, state); + Response.ResponseBuilder location = Response.status(302).location(redirectUri.build()); + Cookie remember = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_REMEMBER_ME); + + Cookie sessionCookie = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_SESSION_COOKIE); + if (sessionCookie != null) { + + String[] split = sessionCookie.getValue().split("/"); + if (split.length >= 3) { + String oldSessionId = split[2]; + if (!oldSessionId.equals(userSession.getId())) { + UserSessionModel oldSession = session.sessions().getUserSession(realm, oldSessionId); + if (oldSession != null) { + log.debugv("Removing old user session: session: {0}", oldSessionId); + session.sessions().removeUserSession(realm, oldSession); + } + } + } + } + + // refresh the cookies! + authManager.createLoginCookie(realm, accessCode.getUser(), userSession, uriInfo, clientConnection); + if (userSession.isRememberMe()) authManager.createRememberMeCookie(realm, uriInfo, clientConnection); + return location.build(); + } + + public Response redirectError(ClientModel client, String error, String state, String redirect) { + UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.ERROR, error); + if (state != null) { + redirectUri.queryParam(OAuth2Constants.STATE, state); + } + return Response.status(302).location(redirectUri.build()).build(); + } + + public Response processAccessCode(String scopeParam, String state, String redirect, ClientModel client, UserModel user, UserSessionModel session, Audit audit) { + isTotpConfigurationRequired(user); + isEmailVerificationRequired(user); + + boolean isResource = client instanceof ApplicationModel; + AccessCode accessCode = tokenManager.createAccessCode(scopeParam, state, redirect, this.session, realm, client, user, session); + + log.debugv("processAccessCode: isResource: {0}", isResource); + log.debugv("processAccessCode: go to oauth page?: {0}", + !isResource); + + audit.detail(Details.CODE_ID, accessCode.getCodeId()); + + Set requiredActions = user.getRequiredActions(); + if (!requiredActions.isEmpty()) { + RequiredAction action = user.getRequiredActions().iterator().next(); + accessCode.setRequiredAction(action); + + if (action.equals(RequiredAction.VERIFY_EMAIL)) { + audit.clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, accessCode.getUser().getEmail()).success(); + } + + return Flows.forms(this.session, realm, client, uriInfo).setAccessCode(accessCode.getCode()).setUser(user) + .createResponse(action); + } + + if (!isResource) { + accessCode.setAction(ClientSessionModel.Action.OAUTH_GRANT); + + List realmRoles = new LinkedList(); + MultivaluedMap resourceRoles = new MultivaluedMapImpl(); + for (RoleModel r : accessCode.getRequestedRoles()) { + if (r.getContainer() instanceof RealmModel) { + realmRoles.add(r); + } else { + resourceRoles.add(((ApplicationModel) r.getContainer()).getName(), r); + } + } + + return Flows.forms(this.session, realm, client, uriInfo) + .setAccessCode(accessCode.getCode()) + .setAccessRequest(realmRoles, resourceRoles) + .setClient(client) + .createOAuthGrant(); + } + + if (redirect != null) { + audit.success(); + + accessCode.setAction(ClientSessionModel.Action.CODE_TO_TOKEN); + return redirectAccessCode(accessCode, session, state, redirect); + } else { + return null; + } + } + + public Response forwardToSecurityFailure(String message) { + return Flows.forms(session, realm, null, uriInfo).setError(message).createErrorPage(); + } + + private void isTotpConfigurationRequired(UserModel user) { + for (RequiredCredentialModel c : realm.getRequiredCredentials()) { + if (c.getType().equals(CredentialRepresentation.TOTP) && !user.isTotp()) { + user.addRequiredAction(RequiredAction.CONFIGURE_TOTP); + log.debug("User is required to configure totp"); + } + } + } + + private void isEmailVerificationRequired(UserModel user) { + if (realm.isVerifyEmail() && !user.isEmailVerified()) { + user.addRequiredAction(RequiredAction.VERIFY_EMAIL); + log.debug("User is required to verify email"); + } + } + +} diff --git a/services/src/main/java/org/keycloak/services/util/CookieHelper.java b/services/src/main/java/org/keycloak/services/util/CookieHelper.java index 6d0d3a4f63..3b30b237ab 100755 --- a/services/src/main/java/org/keycloak/services/util/CookieHelper.java +++ b/services/src/main/java/org/keycloak/services/util/CookieHelper.java @@ -1,9 +1,9 @@ package org.keycloak.services.util; +import org.jboss.resteasy.spi.HttpResponse; import org.jboss.resteasy.spi.ResteasyProviderFactory; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.core.HttpHeaders; /** * @author Bill Burke @@ -24,16 +24,12 @@ public class CookieHelper { * @param httpOnly */ public static void addCookie(String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly) { - HttpServletResponse response = ResteasyProviderFactory.getContextData(HttpServletResponse.class); - Cookie cookie = new Cookie(name, value); - if (path != null) cookie.setPath(path); - if (domain != null) cookie.setDomain(domain); - if (comment != null) cookie.setComment(comment); - cookie.setMaxAge(maxAge); - cookie.setSecure(secure); - cookie.setHttpOnly(httpOnly); - - response.addCookie(cookie); - + HttpResponse response = ResteasyProviderFactory.getContextData(HttpResponse.class); + StringBuffer cookieBuf = new StringBuffer(); + ServerCookie.appendCookieValue(cookieBuf, 1, name, value, path, domain, comment, maxAge, secure, httpOnly); + String cookie = cookieBuf.toString(); + response.getOutputHeaders().add(HttpHeaders.SET_COOKIE, cookie); } + + } diff --git a/services/src/main/java/org/keycloak/services/util/ServerCookie.java b/services/src/main/java/org/keycloak/services/util/ServerCookie.java new file mode 100755 index 0000000000..d41cc2e05b --- /dev/null +++ b/services/src/main/java/org/keycloak/services/util/ServerCookie.java @@ -0,0 +1,305 @@ +package org.keycloak.services.util; + +import java.io.Serializable; +import java.text.DateFormat; +import java.text.FieldPosition; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +/** + * Server-side cookie representation. borrowed from Tomcat. + */ +public class ServerCookie implements Serializable { + private static final String tspecials = ",; "; + private static final String tspecials2 = "()<>@,;:\\\"/[]?={} \t"; + + /* + * Tests a string and returns true if the string counts as a + * reserved token in the Java language. + * + * @param value the String to be tested + * + * @return true if the String is a reserved + * token; false if it is not + */ + public static boolean isToken(String value) { + if (value == null) return true; + int len = value.length(); + + for (int i = 0; i < len; i++) { + char c = value.charAt(i); + + if (tspecials.indexOf(c) != -1) + return false; + } + return true; + } + + public static boolean containsCTL(String value, int version) { + if (value == null) return false; + int len = value.length(); + for (int i = 0; i < len; i++) { + char c = value.charAt(i); + if (c < 0x20 || c >= 0x7f) { + if (c == 0x09) + continue; //allow horizontal tabs + return true; + } + } + return false; + } + + + public static boolean isToken2(String value) { + if (value == null) return true; + int len = value.length(); + + for (int i = 0; i < len; i++) { + char c = value.charAt(i); + if (tspecials2.indexOf(c) != -1) + return false; + } + return true; + } + + /** + * @deprecated - Not used + */ + public static boolean checkName(String name) { + if (!isToken(name) + || name.equalsIgnoreCase("Comment") // rfc2019 + || name.equalsIgnoreCase("Discard") // rfc2965 + || name.equalsIgnoreCase("Domain") // rfc2019 + || name.equalsIgnoreCase("Expires") // Netscape + || name.equalsIgnoreCase("Max-Age") // rfc2019 + || name.equalsIgnoreCase("Path") // rfc2019 + || name.equalsIgnoreCase("Secure") // rfc2019 + || name.equalsIgnoreCase("Version") // rfc2019 + // TODO remaining RFC2965 attributes + ) { + return false; + } + return true; + } + + // -------------------- Cookie parsing tools + + + /** + * Return the header name to set the cookie, based on cookie version. + */ + public static String getCookieHeaderName(int version) { + // TODO Re-enable logging when RFC2965 is implemented + // log( (version==1) ? "Set-Cookie2" : "Set-Cookie"); + if (version == 1) { + // XXX RFC2965 not referenced in Servlet Spec + // Set-Cookie2 is not supported by Netscape 4, 6, IE 3, 5 + // Set-Cookie2 is supported by Lynx and Opera + // Need to check on later IE and FF releases but for now... + // RFC2109 + return "Set-Cookie"; + // return "Set-Cookie2"; + } else { + // Old Netscape + return "Set-Cookie"; + } + } + + /** + * US locale - all HTTP dates are in english + */ + private final static Locale LOCALE_US = Locale.US; + + /** + * GMT timezone - all HTTP dates are on GMT + */ + public final static TimeZone GMT_ZONE = TimeZone.getTimeZone("GMT"); + /** + * Pattern used for old cookies + */ + private final static String OLD_COOKIE_PATTERN = "EEE, dd-MMM-yyyy HH:mm:ss z"; + + + private final static DateFormat oldCookieFormat = new SimpleDateFormat(OLD_COOKIE_PATTERN, LOCALE_US); + + public static String formatOldCookie(Date d) { + String ocf = null; + synchronized (oldCookieFormat) { + ocf = oldCookieFormat.format(d); + } + return ocf; + } + + public static void formatOldCookie(Date d, StringBuffer sb, + FieldPosition fp) { + synchronized (oldCookieFormat) { + oldCookieFormat.format(d, sb, fp); + } + } + + + private static final String ancientDate = formatOldCookie(new Date(10000)); + + + // TODO RFC2965 fields also need to be passed + public static void appendCookieValue(StringBuffer headerBuf, + int version, + String name, + String value, + String path, + String domain, + String comment, + int maxAge, + boolean isSecure, + boolean httpOnly) { + StringBuffer buf = new StringBuffer(); + // Servlet implementation checks name + buf.append(name); + buf.append("="); + // Servlet implementation does not check anything else + + // NOTE!!! BROWSERS REALLY DON'T LIKE QUOTING + //maybeQuote2(version, buf, value); + buf.append(value); + + // Add version 1 specific information + if (version == 1) { + // Version=1 ... required + buf.append("; Version=1"); + + // Comment=comment + if (comment != null) { + buf.append("; Comment="); + //maybeQuote2(version, buf, comment); + buf.append(comment); + } + } + + // Add domain information, if present + if (domain != null) { + buf.append("; Domain="); + //maybeQuote2(version, buf, domain); + buf.append(domain); + } + + // Max-Age=secs ... or use old "Expires" format + // TODO RFC2965 Discard + if (maxAge >= 0) { + // Wdy, DD-Mon-YY HH:MM:SS GMT ( Expires Netscape format ) + buf.append("; Expires="); + // To expire immediately we need to set the time in past + if (maxAge == 0) + buf.append(ancientDate); + else + formatOldCookie + (new Date(System.currentTimeMillis() + + maxAge * 1000L), buf, + new FieldPosition(0)); + + buf.append("; Max-Age="); + buf.append(maxAge); + } + + // Path=path + if (path != null) { + buf.append("; Path="); + buf.append(path); + } + + // Secure + if (isSecure) { + buf.append("; Secure"); + } + + // HttpOnly + if (httpOnly) { + buf.append("; HttpOnly"); + } + + headerBuf.append(buf); + } + + /** + * @deprecated - Not used + */ + @Deprecated + public static void maybeQuote(int version, StringBuffer buf, String value) { + // special case - a \n or \r shouldn't happen in any case + if (isToken(value)) { + buf.append(value); + } else { + buf.append('"'); + buf.append(escapeDoubleQuotes(value, 0, value.length())); + buf.append('"'); + } + } + + public static boolean alreadyQuoted(String value) { + if (value == null || value.length() == 0) return false; + return (value.charAt(0) == '\"' && value.charAt(value.length() - 1) == '\"'); + } + + /** + * Quotes values using rules that vary depending on Cookie version. + * + * @param version + * @param buf + * @param value + */ + public static void maybeQuote2(int version, StringBuffer buf, String value) { + if (value == null || value.length() == 0) { + buf.append("\"\""); + } else if (containsCTL(value, version)) + throw new IllegalArgumentException("Control character in cookie value, consider BASE64 encoding your value"); + else if (alreadyQuoted(value)) { + buf.append('"'); + buf.append(escapeDoubleQuotes(value, 1, value.length() - 1)); + buf.append('"'); + } else if (version == 0 && !isToken(value)) { + buf.append('"'); + buf.append(escapeDoubleQuotes(value, 0, value.length())); + buf.append('"'); + } else if (version == 1 && !isToken2(value)) { + buf.append('"'); + buf.append(escapeDoubleQuotes(value, 0, value.length())); + buf.append('"'); + } else { + buf.append(value); + } + } + + + /** + * Escapes any double quotes in the given string. + * + * @param s the input string + * @param beginIndex start index inclusive + * @param endIndex exclusive + * @return The (possibly) escaped string + */ + private static String escapeDoubleQuotes(String s, int beginIndex, int endIndex) { + + if (s == null || s.length() == 0 || s.indexOf('"') == -1) { + return s; + } + + StringBuffer b = new StringBuffer(); + for (int i = beginIndex; i < endIndex; i++) { + char c = s.charAt(i); + if (c == '\\') { + b.append(c); + //ignore the character after an escape, just append it + if (++i >= endIndex) throw new IllegalArgumentException("Invalid escape character in cookie value."); + b.append(s.charAt(i)); + } else if (c == '"') + b.append('\\').append('"'); + else + b.append(c); + } + + return b.toString(); + } + +} diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java index 99f6558117..9b4859027b 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java @@ -3,6 +3,7 @@ package org.keycloak.testsuite.forms; import org.junit.Assert; import org.junit.ClassRule; import org.junit.FixMethodOrder; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.RuleChain; @@ -102,6 +103,13 @@ public class FederationProvidersIntegrationTest { @WebResource protected AccountPasswordPage changePasswordPage; + @Test + @Ignore + public void runit() throws Exception { + Thread.sleep(10000000); + + } + static UserModel addUser(KeycloakSession session, RealmModel realm, String username, String email, String password) { UserModel user = session.users().addUser(realm, username); user.setEmail(email); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/perf/AccessTokenPerfTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/perf/AccessTokenPerfTest.java index b2d80fc5c6..686b785e75 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/perf/AccessTokenPerfTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/perf/AccessTokenPerfTest.java @@ -180,6 +180,11 @@ public class AccessTokenPerfTest { } Assert.assertEquals(302, response.getStatus()); uri = response.getLocation(); + for (String header : response.getHeaders().keySet()) { + for (Object value : response.getHeaders().get(header)) { + System.out.println(header + ": " + value); + } + } response.close(); Assert.assertNotNull(uri);