/*
* 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.protocol;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.jboss.logging.Logger;
import org.keycloak.common.ClientConnection;
import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.crypto.HMACProvider;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.AuthenticationSessionManager;
import org.keycloak.services.util.CookieHelper;
import org.keycloak.sessions.AuthenticationSessionModel;
import javax.crypto.SecretKey;
import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.UriInfo;
import java.util.HashMap;
import java.util.Map;
/**
* This is an an encoded token that is stored as a cookie so that if there is a client timeout, then the authentication session
* can be restarted.
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class RestartLoginCookie {
private static final Logger logger = Logger.getLogger(RestartLoginCookie.class);
public static final String KC_RESTART = "KC_RESTART";
@JsonProperty("cid")
protected String clientId;
@JsonProperty("pty")
protected String authMethod;
@JsonProperty("ruri")
protected String redirectUri;
@JsonProperty("act")
protected String action;
@JsonProperty("notes")
protected Map<String, String> notes = new HashMap<>();
public Map<String, String> getNotes() {
return notes;
}
public void setNotes(Map<String, String> notes) {
this.notes = notes;
}
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getAuthMethod() {
return authMethod;
}
public void setAuthMethod(String authMethod) {
this.authMethod = authMethod;
}
public String getRedirectUri() {
return redirectUri;
}
public void setRedirectUri(String redirectUri) {
this.redirectUri = redirectUri;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public String encode(KeycloakSession session, RealmModel realm) {
KeyManager.ActiveHmacKey activeKey = session.keys().getActiveHmacKey(realm);
JWSBuilder builder = new JWSBuilder();
return builder.kid(activeKey.getKid()).jsonContent(this)
.hmac256(activeKey.getSecretKey());
}
public RestartLoginCookie() {
}
public RestartLoginCookie(AuthenticationSessionModel clientSession) {
this.action = clientSession.getAction();
this.clientId = clientSession.getClient().getClientId();
this.authMethod = clientSession.getProtocol();
this.redirectUri = clientSession.getRedirectUri();
for (Map.Entry<String, String> entry : clientSession.getClientNotes().entrySet()) {
notes.put(entry.getKey(), entry.getValue());
}
}
public static void setRestartCookie(KeycloakSession session, RealmModel realm, ClientConnection connection, UriInfo uriInfo, AuthenticationSessionModel authSession) {
RestartLoginCookie restart = new RestartLoginCookie(authSession);
String encoded = restart.encode(session, realm);
String path = AuthenticationManager.getRealmCookiePath(realm, uriInfo);
boolean secureOnly = realm.getSslRequired().isRequired(connection);
CookieHelper.addCookie(KC_RESTART, encoded, path, null, null, -1, secureOnly, true);
}
public static void expireRestartCookie(RealmModel realm, ClientConnection connection, UriInfo uriInfo) {
String path = AuthenticationManager.getRealmCookiePath(realm, uriInfo);
boolean secureOnly = realm.getSslRequired().isRequired(connection);
CookieHelper.addCookie(KC_RESTART, "", path, null, null, 0, secureOnly, true);
}
public static AuthenticationSessionModel restartSession(KeycloakSession session, RealmModel realm) throws Exception {
Cookie cook = session.getContext().getRequestHeaders().getCookies().get(KC_RESTART);
if (cook == null) {
logger.debug("KC_RESTART cookie doesn't exist");
return null;
}
String encodedCookie = cook.getValue();
JWSInput input = new JWSInput(encodedCookie);
SecretKey secretKey = session.keys().getHmacSecretKey(realm, input.getHeader().getKeyId());
if (!HMACProvider.verify(input, secretKey)) {
logger.debug("Failed to verify encoded RestartLoginCookie");
return null;
}
RestartLoginCookie cookie = input.readJsonContent(RestartLoginCookie.class);
ClientModel client = realm.getClientByClientId(cookie.getClientId());
if (client == null) return null;
AuthenticationSessionModel authSession = new AuthenticationSessionManager(session).createAuthenticationSession(realm, client, true);
authSession.setProtocol(cookie.getAuthMethod());
authSession.setRedirectUri(cookie.getRedirectUri());
authSession.setAction(cookie.getAction());
for (Map.Entry<String, String> entry : cookie.getNotes().entrySet()) {
authSession.setClientNote(entry.getKey(), entry.getValue());
}
return authSession;
}
}