KEYCLOAK-9050 Change LoginProtocol.authenticated to read most of the values from authenticationSession

This commit is contained in:
mposolda 2018-12-11 18:49:29 +01:00 committed by Marek Posolda
parent 3ed77825a2
commit c51c492996
12 changed files with 66 additions and 38 deletions

View file

@ -68,7 +68,7 @@ public interface LoginProtocol extends Provider {
LoginProtocol setEventBuilder(EventBuilder event); LoginProtocol setEventBuilder(EventBuilder event);
Response authenticated(UserSessionModel userSession, ClientSessionContext clientSessionCtx); Response authenticated(AuthenticationSessionModel authSession, UserSessionModel userSession, ClientSessionContext clientSessionCtx);
Response sendError(AuthenticationSessionModel authSession, Error error); Response sendError(AuthenticationSessionModel authSession, Error error);

View file

@ -45,6 +45,4 @@ public interface ClientSessionContext {
<T> T getAttribute(String attribute, Class<T> clazz); <T> T getAttribute(String attribute, Class<T> clazz);
String AUTHENTICATION_SESSION_ATTR = "AUTH_SESSION_ATTR";
} }

View file

@ -979,7 +979,7 @@ public class AuthenticationProcessor {
event.success(); event.success();
RealmModel realm = authenticationSession.getRealm(); RealmModel realm = authenticationSession.getRealm();
ClientSessionContext clientSessionCtx = attachSession(); ClientSessionContext clientSessionCtx = attachSession();
return AuthenticationManager.redirectAfterSuccessfulFlow(session, realm, userSession, clientSessionCtx, request, uriInfo, connection, event, protocol); return AuthenticationManager.redirectAfterSuccessfulFlow(session, realm, userSession, clientSessionCtx, request, uriInfo, connection, event, authenticationSession, protocol);
} }

View file

@ -92,7 +92,7 @@ public class DockerAuthV2Protocol implements LoginProtocol {
} }
@Override @Override
public Response authenticated(final UserSessionModel userSession, final ClientSessionContext clientSessionCtx) { public Response authenticated(final AuthenticationSessionModel authSession, final UserSessionModel userSession, final ClientSessionContext clientSessionCtx) {
// First, create a base response token with realm + user values populated // First, create a base response token with realm + user values populated
final AuthenticatedClientSessionModel clientSession = clientSessionCtx.getClientSession(); final AuthenticatedClientSessionModel clientSession = clientSessionCtx.getClientSession();
final ClientModel client = clientSession.getClient(); final ClientModel client = clientSession.getClient();
@ -100,7 +100,7 @@ public class DockerAuthV2Protocol implements LoginProtocol {
DockerResponseToken responseToken = new DockerResponseToken() DockerResponseToken responseToken = new DockerResponseToken()
.id(KeycloakModelUtils.generateId()) .id(KeycloakModelUtils.generateId())
.type(TokenUtil.TOKEN_TYPE_BEARER) .type(TokenUtil.TOKEN_TYPE_BEARER)
.issuer(clientSession.getNote(DockerAuthV2Protocol.ISSUER)) .issuer(authSession.getClientNote(DockerAuthV2Protocol.ISSUER))
.subject(userSession.getUser().getUsername()) .subject(userSession.getUser().getUsername())
.issuedNow() .issuedNow()
.audience(client.getClientId()) .audience(client.getClientId())

View file

@ -181,16 +181,16 @@ public class OIDCLoginProtocol implements LoginProtocol {
@Override @Override
public Response authenticated(UserSessionModel userSession, ClientSessionContext clientSessionCtx) { public Response authenticated(AuthenticationSessionModel authSession, UserSessionModel userSession, ClientSessionContext clientSessionCtx) {
AuthenticatedClientSessionModel clientSession= clientSessionCtx.getClientSession(); AuthenticatedClientSessionModel clientSession= clientSessionCtx.getClientSession();
String responseTypeParam = clientSession.getNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM); String responseTypeParam = authSession.getClientNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
String responseModeParam = clientSession.getNote(OIDCLoginProtocol.RESPONSE_MODE_PARAM); String responseModeParam = authSession.getClientNote(OIDCLoginProtocol.RESPONSE_MODE_PARAM);
setupResponseTypeAndMode(responseTypeParam, responseModeParam); setupResponseTypeAndMode(responseTypeParam, responseModeParam);
String redirect = clientSession.getRedirectUri(); String redirect = authSession.getRedirectUri();
OIDCRedirectUriBuilder redirectUri = OIDCRedirectUriBuilder.fromUri(redirect, responseMode); OIDCRedirectUriBuilder redirectUri = OIDCRedirectUriBuilder.fromUri(redirect, responseMode);
String state = clientSession.getNote(OIDCLoginProtocol.STATE_PARAM); String state = authSession.getClientNote(OIDCLoginProtocol.STATE_PARAM);
logger.debugv("redirectAccessCode: state: {0}", state); logger.debugv("redirectAccessCode: state: {0}", state);
if (state != null) if (state != null)
redirectUri.addParam(OAuth2Constants.STATE, state); redirectUri.addParam(OAuth2Constants.STATE, state);
@ -200,12 +200,6 @@ public class OIDCLoginProtocol implements LoginProtocol {
redirectUri.addParam(OAuth2Constants.SESSION_STATE, userSession.getId()); redirectUri.addParam(OAuth2Constants.SESSION_STATE, userSession.getId());
} }
AuthenticationSessionModel authSession = clientSessionCtx.getAttribute(ClientSessionContext.AUTHENTICATION_SESSION_ATTR, AuthenticationSessionModel.class);
if (authSession == null) {
// Shouldn't happen if correctly used
throw new IllegalStateException("AuthenticationSession attachement not set in the ClientSessionContext");
}
String nonce = authSession.getClientNote(OIDCLoginProtocol.NONCE_PARAM); String nonce = authSession.getClientNote(OIDCLoginProtocol.NONCE_PARAM);
clientSessionCtx.setAttribute(OIDCLoginProtocol.NONCE_PARAM, nonce); clientSessionCtx.setAttribute(OIDCLoginProtocol.NONCE_PARAM, nonce);

View file

@ -431,8 +431,6 @@ public class TokenManager {
new AuthenticationSessionManager(session).removeAuthenticationSession(userSession.getRealm(), authSession, true); new AuthenticationSessionManager(session).removeAuthenticationSession(userSession.getRealm(), authSession, true);
ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionAndClientScopeIds(clientSession, clientScopeIds); ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionAndClientScopeIds(clientSession, clientScopeIds);
clientSessionCtx.setAttribute(ClientSessionContext.AUTHENTICATION_SESSION_ATTR, authSession);
return clientSessionCtx; return clientSessionCtx;
} }

View file

@ -297,8 +297,8 @@ public class SamlProtocol implements LoginProtocol {
return (logoutRedirectUrl == null || logoutRedirectUrl.trim().isEmpty()); return (logoutRedirectUrl == null || logoutRedirectUrl.trim().isEmpty());
} }
protected String getNameIdFormat(SamlClient samlClient, AuthenticatedClientSessionModel clientSession) { protected String getNameIdFormat(SamlClient samlClient, AuthenticationSessionModel authSession) {
String nameIdFormat = clientSession.getNote(GeneralConstants.NAMEID_FORMAT); String nameIdFormat = authSession.getClientNote(GeneralConstants.NAMEID_FORMAT);
boolean forceFormat = samlClient.forceNameIDFormat(); boolean forceFormat = samlClient.forceNameIDFormat();
String configuredNameIdFormat = samlClient.getNameIDFormat(); String configuredNameIdFormat = samlClient.getNameIDFormat();
@ -368,20 +368,20 @@ public class SamlProtocol implements LoginProtocol {
} }
@Override @Override
public Response authenticated(UserSessionModel userSession, ClientSessionContext clientSessionCtx) { public Response authenticated(AuthenticationSessionModel authSession, UserSessionModel userSession, ClientSessionContext clientSessionCtx) {
AuthenticatedClientSessionModel clientSession = clientSessionCtx.getClientSession(); AuthenticatedClientSessionModel clientSession = clientSessionCtx.getClientSession();
ClientModel client = clientSession.getClient(); ClientModel client = clientSession.getClient();
SamlClient samlClient = new SamlClient(client); SamlClient samlClient = new SamlClient(client);
String requestID = clientSession.getNote(SAML_REQUEST_ID); String requestID = authSession.getClientNote(SAML_REQUEST_ID);
String relayState = clientSession.getNote(GeneralConstants.RELAY_STATE); String relayState = authSession.getClientNote(GeneralConstants.RELAY_STATE);
String redirectUri = clientSession.getRedirectUri(); String redirectUri = authSession.getRedirectUri();
String responseIssuer = getResponseIssuer(realm); String responseIssuer = getResponseIssuer(realm);
String nameIdFormat = getNameIdFormat(samlClient, clientSession); String nameIdFormat = getNameIdFormat(samlClient, authSession);
String nameId = getNameId(nameIdFormat, clientSession, userSession); String nameId = getNameId(nameIdFormat, authSession, userSession);
if (nameId == null) { if (nameId == null) {
return samlErrorMessage( return samlErrorMessage(
null, samlClient, isPostBinding(clientSession), null, samlClient, isPostBinding(authSession),
redirectUri, JBossSAMLURIConstants.STATUS_INVALID_NAMEIDPOLICY, relayState redirectUri, JBossSAMLURIConstants.STATUS_INVALID_NAMEIDPOLICY, relayState
); );
} }
@ -426,7 +426,7 @@ public class SamlProtocol implements LoginProtocol {
Document samlDocument = null; Document samlDocument = null;
KeyManager keyManager = session.keys(); KeyManager keyManager = session.keys();
KeyManager.ActiveRsaKey keys = keyManager.getActiveRsaKey(realm); KeyManager.ActiveRsaKey keys = keyManager.getActiveRsaKey(realm);
boolean postBinding = isPostBinding(clientSession); boolean postBinding = isPostBinding(authSession);
String keyName = samlClient.getXmlSigKeyInfoKeyNameTransformer().getKeyName(keys.getKid(), keys.getCertificate()); String keyName = samlClient.getXmlSigKeyInfoKeyNameTransformer().getKeyName(keys.getKid(), keys.getCertificate());
try { try {

View file

@ -733,20 +733,20 @@ public class AuthenticationManager {
public static Response redirectAfterSuccessfulFlow(KeycloakSession session, RealmModel realm, UserSessionModel userSession, public static Response redirectAfterSuccessfulFlow(KeycloakSession session, RealmModel realm, UserSessionModel userSession,
ClientSessionContext clientSessionCtx, ClientSessionContext clientSessionCtx,
HttpRequest request, UriInfo uriInfo, ClientConnection clientConnection, HttpRequest request, UriInfo uriInfo, ClientConnection clientConnection,
EventBuilder event, String protocol) { EventBuilder event, AuthenticationSessionModel authSession) {
LoginProtocol protocolImpl = session.getProvider(LoginProtocol.class, protocol); LoginProtocol protocolImpl = session.getProvider(LoginProtocol.class, authSession.getProtocol());
protocolImpl.setRealm(realm) protocolImpl.setRealm(realm)
.setHttpHeaders(request.getHttpHeaders()) .setHttpHeaders(request.getHttpHeaders())
.setUriInfo(uriInfo) .setUriInfo(uriInfo)
.setEventBuilder(event); .setEventBuilder(event);
return redirectAfterSuccessfulFlow(session, realm, userSession, clientSessionCtx, request, uriInfo, clientConnection, event, protocolImpl); return redirectAfterSuccessfulFlow(session, realm, userSession, clientSessionCtx, request, uriInfo, clientConnection, event, authSession, protocolImpl);
} }
public static Response redirectAfterSuccessfulFlow(KeycloakSession session, RealmModel realm, UserSessionModel userSession, public static Response redirectAfterSuccessfulFlow(KeycloakSession session, RealmModel realm, UserSessionModel userSession,
ClientSessionContext clientSessionCtx, ClientSessionContext clientSessionCtx,
HttpRequest request, UriInfo uriInfo, ClientConnection clientConnection, HttpRequest request, UriInfo uriInfo, ClientConnection clientConnection,
EventBuilder event, LoginProtocol protocol) { EventBuilder event, AuthenticationSessionModel authSession, LoginProtocol protocol) {
Cookie sessionCookie = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_SESSION_COOKIE); Cookie sessionCookie = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_SESSION_COOKIE);
if (sessionCookie != null) { if (sessionCookie != null) {
@ -787,7 +787,7 @@ public class AuthenticationManager {
clientSession.removeNote(SSO_AUTH); clientSession.removeNote(SSO_AUTH);
} }
return protocol.authenticated(userSession, clientSessionCtx); return protocol.authenticated(authSession, userSession, clientSessionCtx);
} }
@ -873,7 +873,7 @@ public class AuthenticationManager {
event.event(EventType.LOGIN); event.event(EventType.LOGIN);
event.session(userSession); event.session(userSession);
event.success(); event.success();
return redirectAfterSuccessfulFlow(session, realm, userSession, clientSessionCtx, request, uriInfo, clientConnection, event, authSession.getProtocol()); return redirectAfterSuccessfulFlow(session, realm, userSession, clientSessionCtx, request, uriInfo, clientConnection, event, authSession);
} }
// Return null if action is not required. Or the name of the requiredAction in case it is required. // Return null if action is not required. Or the name of the requiredAction in case it is required.

View file

@ -864,7 +864,7 @@ public class LoginActionsService {
event.success(); event.success();
ClientSessionContext clientSessionCtx = AuthenticationProcessor.attachSession(authSession, null, session, realm, clientConnection, event); ClientSessionContext clientSessionCtx = AuthenticationProcessor.attachSession(authSession, null, session, realm, clientConnection, event);
return AuthenticationManager.redirectAfterSuccessfulFlow(session, realm, clientSessionCtx.getClientSession().getUserSession(), clientSessionCtx, request, session.getContext().getUri(), clientConnection, event, authSession.getProtocol()); return AuthenticationManager.redirectAfterSuccessfulFlow(session, realm, clientSessionCtx.getClientSession().getUserSession(), clientSessionCtx, request, session.getContext().getUri(), clientConnection, event, authSession);
} }
private void initLoginEvent(AuthenticationSessionModel authSession) { private void initLoginEvent(AuthenticationSessionModel authSession) {

View file

@ -760,6 +760,10 @@ public class OAuthClient {
return redirectUri; return redirectUri;
} }
public String getState() {
return state.getState();
}
public String getNonce() { public String getNonce() {
return nonce; return nonce;
} }

View file

@ -326,7 +326,8 @@ public class ConcurrentLoginTest extends AbstractConcurrencyTest {
OAuthClient oauth1 = new OAuthClient(); OAuthClient oauth1 = new OAuthClient();
oauth1.init(driver); oauth1.init(driver);
// Add some randomness to nonce and redirectUri. Verify that login is successful and nonce will match // Add some randomness to state, nonce and redirectUri. Verify that login is successful and "state" and "nonce" will match
oauth1.stateParamHardcoded(KeycloakModelUtils.generateId());
oauth1.nonce(KeycloakModelUtils.generateId()); oauth1.nonce(KeycloakModelUtils.generateId());
oauth1.redirectUri(oauth.getRedirectUri() + "?some=" + new Random().nextInt(1024)); oauth1.redirectUri(oauth.getRedirectUri() + "?some=" + new Random().nextInt(1024));
return oauth1; return oauth1;
@ -371,7 +372,12 @@ public class ConcurrentLoginTest extends AbstractConcurrencyTest {
Assert.assertThat(context.getRedirectLocations(), Matchers.notNullValue()); Assert.assertThat(context.getRedirectLocations(), Matchers.notNullValue());
Assert.assertThat(context.getRedirectLocations(), Matchers.not(Matchers.empty())); Assert.assertThat(context.getRedirectLocations(), Matchers.not(Matchers.empty()));
String currentUrl = context.getRedirectLocations().get(0).toString(); String currentUrl = context.getRedirectLocations().get(0).toString();
String code = getQueryFromUrl(currentUrl).get(OAuth2Constants.CODE);
Map<String, String> query = getQueryFromUrl(currentUrl);
String code = query.get(OAuth2Constants.CODE);
String state = query.get(OAuth2Constants.STATE);
Assert.assertEquals("Invalid state.", state, oauth1.getState());
AtomicReference<OAuthClient.AccessTokenResponse> accessResRef = new AtomicReference<>(); AtomicReference<OAuthClient.AccessTokenResponse> accessResRef = new AtomicReference<>();
totalInvocations.incrementAndGet(); totalInvocations.incrementAndGet();

View file

@ -37,6 +37,7 @@ import org.openqa.selenium.By;
import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriBuilder;
import java.io.IOException; import java.io.IOException;
import java.net.URI;
import java.util.List; import java.util.List;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@ -169,4 +170,31 @@ public class AuthorizationCodeTest extends AbstractKeycloakTest {
String codeId = events.expectLogin().assertEvent().getDetails().get(Details.CODE_ID); String codeId = events.expectLogin().assertEvent().getDetails().get(Details.CODE_ID);
} }
@Test
public void authorizationRequestFragmentResponseModeNotKept() throws Exception {
// Set response_mode=fragment and login
oauth.responseMode(OIDCResponseMode.FRAGMENT.toString().toLowerCase());
OAuthClient.AuthorizationEndpointResponse response = oauth.doLogin("test-user@localhost", "password");
Assert.assertNotNull(response.getCode());
Assert.assertNotNull(response.getState());
URI currentUri = new URI(driver.getCurrentUrl());
Assert.assertNull(currentUri.getRawQuery());
Assert.assertNotNull(currentUri.getRawFragment());
// Unset response_mode. The initial OIDC AuthenticationRequest won't contain "response_mode" parameter now and hence it should fallback to "query".
oauth.responseMode(null);
oauth.openLoginForm();
response = new OAuthClient.AuthorizationEndpointResponse(oauth);
Assert.assertNotNull(response.getCode());
Assert.assertNotNull(response.getState());
currentUri = new URI(driver.getCurrentUrl());
Assert.assertNotNull(currentUri.getRawQuery());
Assert.assertNull(currentUri.getRawFragment());
}
} }