KEYCLOAK-4016 Provide a Link to go Back to The Application on a Timeout
This commit is contained in:
parent
ca8577fb4a
commit
8adde64e2c
38 changed files with 541 additions and 191 deletions
|
@ -326,6 +326,10 @@ public class LDAPOperationManager {
|
|||
filter = "(&(objectClass=*)(" + getUuidAttributeName() + LDAPConstants.EQUAL + id + "))";
|
||||
}
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.tracef("Using filter for lookup user by LDAP ID: %s", filter);
|
||||
}
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package org.keycloak.broker.provider;
|
||||
|
||||
import org.jboss.resteasy.spi.HttpRequest;
|
||||
import org.keycloak.broker.provider.util.IdentityBrokerState;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||
|
@ -30,13 +31,13 @@ public class AuthenticationRequest {
|
|||
|
||||
private final KeycloakSession session;
|
||||
private final UriInfo uriInfo;
|
||||
private final String state;
|
||||
private final IdentityBrokerState state;
|
||||
private final HttpRequest httpRequest;
|
||||
private final RealmModel realm;
|
||||
private final String redirectUri;
|
||||
private final AuthenticationSessionModel authSession;
|
||||
|
||||
public AuthenticationRequest(KeycloakSession session, RealmModel realm, AuthenticationSessionModel authSession, HttpRequest httpRequest, UriInfo uriInfo, String state, String redirectUri) {
|
||||
public AuthenticationRequest(KeycloakSession session, RealmModel realm, AuthenticationSessionModel authSession, HttpRequest httpRequest, UriInfo uriInfo, IdentityBrokerState state, String redirectUri) {
|
||||
this.session = session;
|
||||
this.realm = realm;
|
||||
this.httpRequest = httpRequest;
|
||||
|
@ -54,7 +55,7 @@ public class AuthenticationRequest {
|
|||
return this.uriInfo;
|
||||
}
|
||||
|
||||
public String getState() {
|
||||
public IdentityBrokerState getState() {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* 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.broker.provider.util;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Encapsulates parsing logic related to state passed to identity provider in "state" (or RelayState) parameter
|
||||
*
|
||||
* Not Thread-safe
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class IdentityBrokerState {
|
||||
|
||||
private String decodedState;
|
||||
private String clientId;
|
||||
private String encodedState;
|
||||
|
||||
private IdentityBrokerState() {
|
||||
}
|
||||
|
||||
public static IdentityBrokerState decoded(String decodedState, String clientId) {
|
||||
IdentityBrokerState state = new IdentityBrokerState();
|
||||
state.decodedState = decodedState;
|
||||
state.clientId = clientId;
|
||||
return state;
|
||||
}
|
||||
|
||||
public static IdentityBrokerState encoded(String encodedState) {
|
||||
IdentityBrokerState state = new IdentityBrokerState();
|
||||
state.encodedState = encodedState;
|
||||
return state;
|
||||
}
|
||||
|
||||
|
||||
public String getDecodedState() {
|
||||
if (decodedState == null) {
|
||||
decode();
|
||||
}
|
||||
return decodedState;
|
||||
}
|
||||
|
||||
public String getClientId() {
|
||||
if (decodedState == null) {
|
||||
decode();
|
||||
}
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public String getEncodedState() {
|
||||
if (encodedState == null) {
|
||||
encode();
|
||||
}
|
||||
return encodedState;
|
||||
}
|
||||
|
||||
|
||||
private void decode() {
|
||||
String[] decoded = DOT.split(encodedState, 0);
|
||||
decodedState = decoded[0];
|
||||
if (decoded.length > 0) {
|
||||
clientId = decoded[1];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void encode() {
|
||||
encodedState = decodedState + "." + clientId;
|
||||
}
|
||||
|
||||
private static final Pattern DOT = Pattern.compile("\\.");
|
||||
|
||||
|
||||
}
|
|
@ -22,7 +22,6 @@ import org.keycloak.models.RoleModel;
|
|||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.FormMessage;
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
|
|
@ -52,8 +52,12 @@ public interface Constants {
|
|||
int DEFAULT_OFFLINE_SESSION_IDLE_TIMEOUT = 2592000;
|
||||
|
||||
String VERIFY_EMAIL_KEY = "VERIFY_EMAIL_KEY";
|
||||
String EXECUTION = "execution";
|
||||
String CLIENT_ID = "client_id";
|
||||
String KEY = "key";
|
||||
|
||||
String SKIP_LINK = "skipLink";
|
||||
|
||||
// Prefix for user attributes used in various "context"data maps
|
||||
String USER_ATTRIBUTES_PREFIX = "user.attributes.";
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ import org.keycloak.models.AuthenticationFlowModel;
|
|||
import org.keycloak.models.AuthenticatorConfigModel;
|
||||
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
@ -485,15 +486,17 @@ public class AuthenticationProcessor {
|
|||
return LoginActionsService.loginActionsBaseUrl(getUriInfo())
|
||||
.path(AuthenticationProcessor.this.flowPath)
|
||||
.queryParam(OAuth2Constants.CODE, code)
|
||||
.queryParam("execution", getExecution().getId())
|
||||
.queryParam(Constants.EXECUTION, getExecution().getId())
|
||||
.queryParam(Constants.CLIENT_ID, getAuthenticationSession().getClient().getClientId())
|
||||
.build(getRealm().getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getActionTokenUrl(String tokenString) {
|
||||
return LoginActionsService.actionTokenProcessor(getUriInfo())
|
||||
.queryParam("key", tokenString)
|
||||
.queryParam("execution", getExecution().getId())
|
||||
.queryParam(Constants.KEY, tokenString)
|
||||
.queryParam(Constants.EXECUTION, getExecution().getId())
|
||||
.queryParam(Constants.CLIENT_ID, getAuthenticationSession().getClient().getClientId())
|
||||
.build(getRealm().getName());
|
||||
}
|
||||
|
||||
|
@ -501,7 +504,8 @@ public class AuthenticationProcessor {
|
|||
public URI getRefreshExecutionUrl() {
|
||||
return LoginActionsService.loginActionsBaseUrl(getUriInfo())
|
||||
.path(AuthenticationProcessor.this.flowPath)
|
||||
.queryParam("execution", getExecution().getId())
|
||||
.queryParam(Constants.EXECUTION, getExecution().getId())
|
||||
.queryParam(Constants.CLIENT_ID, getAuthenticationSession().getClient().getClientId())
|
||||
.build(getRealm().getName());
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,8 @@ import org.keycloak.events.EventBuilder;
|
|||
import org.keycloak.forms.login.LoginFormsProvider;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.AuthenticatorConfigModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
@ -247,9 +249,11 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
|
|||
}
|
||||
|
||||
public URI getActionUrl(String executionId, String code) {
|
||||
ClientModel client = processor.getAuthenticationSession().getClient();
|
||||
return LoginActionsService.registrationFormProcessor(processor.getUriInfo())
|
||||
.queryParam(OAuth2Constants.CODE, code)
|
||||
.queryParam("execution", executionId)
|
||||
.queryParam(Constants.EXECUTION, executionId)
|
||||
.queryParam(Constants.CLIENT_ID, client.getClientId())
|
||||
.build(processor.getRealm().getName());
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,8 @@ import org.keycloak.common.ClientConnection;
|
|||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.forms.login.LoginFormsProvider;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
@ -132,9 +134,11 @@ public class RequiredActionContextResult implements RequiredActionContext {
|
|||
|
||||
@Override
|
||||
public URI getActionUrl(String code) {
|
||||
ClientModel client = authenticationSession.getClient();
|
||||
return LoginActionsService.requiredActionProcessor(getUriInfo())
|
||||
.queryParam(OAuth2Constants.CODE, code)
|
||||
.queryParam("execution", factory.getId())
|
||||
.queryParam(Constants.EXECUTION, factory.getId())
|
||||
.queryParam(Constants.CLIENT_ID, client.getClientId())
|
||||
.build(getRealm().getName());
|
||||
}
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ public class ActionTokenContext<T extends JsonWebToken> {
|
|||
|
||||
@FunctionalInterface
|
||||
public interface ProcessBrokerFlow {
|
||||
Response brokerLoginFlow(String code, String execution, String flowPath);
|
||||
Response brokerLoginFlow(String code, String execution, String clientId, String flowPath);
|
||||
};
|
||||
|
||||
private final KeycloakSession session;
|
||||
|
@ -158,6 +158,7 @@ public class ActionTokenContext<T extends JsonWebToken> {
|
|||
}
|
||||
|
||||
public Response brokerFlow(String code, String flowPath) {
|
||||
return processBrokerFlow.brokerLoginFlow(code, getExecutionId(), flowPath);
|
||||
ClientModel client = authenticationSession.getClient();
|
||||
return processBrokerFlow.brokerLoginFlow(code, getExecutionId(), client.getClientId(), flowPath);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import org.keycloak.authentication.actiontoken.*;
|
|||
import org.keycloak.authentication.authenticators.broker.IdpEmailVerificationAuthenticator;
|
||||
import org.keycloak.events.*;
|
||||
import org.keycloak.forms.login.LoginFormsProvider;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.services.managers.AuthenticationSessionManager;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
|
@ -86,7 +87,7 @@ public class IdpVerifyAccountLinkActionTokenHandler extends AbstractActionTokenH
|
|||
|
||||
return tokenContext.getSession().getProvider(LoginFormsProvider.class)
|
||||
.setSuccess(Messages.IDENTITY_PROVIDER_LINK_SUCCESS, token.getIdentityProviderAlias(), token.getIdentityProviderUsername())
|
||||
.setAttribute("skipLink", true)
|
||||
.setAttribute(Constants.SKIP_LINK, true)
|
||||
.createInfoPage();
|
||||
}
|
||||
|
||||
|
|
|
@ -132,7 +132,10 @@ public class IdpEmailVerificationAuthenticator extends AbstractIdpAuthenticator
|
|||
brokerContext.getUsername(), brokerContext.getIdpConfig().getAlias()
|
||||
);
|
||||
UriBuilder builder = Urls.actionTokenBuilder(uriInfo.getBaseUri(), token.serialize(session, realm, uriInfo));
|
||||
String link = builder.queryParam("execution", context.getExecution().getId()).build(realm.getName()).toString();
|
||||
String link = builder
|
||||
.queryParam(Constants.EXECUTION, context.getExecution().getId())
|
||||
.queryParam(Constants.CLIENT_ID, context.getExecution().getId())
|
||||
.build(realm.getName()).toString();
|
||||
long expirationInMinutes = TimeUnit.SECONDS.toMinutes(validityInSecs);
|
||||
|
||||
try {
|
||||
|
|
|
@ -64,8 +64,9 @@ public class IdentityProviderAuthenticator implements Authenticator {
|
|||
for (IdentityProviderModel identityProvider : identityProviders) {
|
||||
if (identityProvider.isEnabled() && providerId.equals(identityProvider.getAlias())) {
|
||||
String accessCode = new ClientSessionCode<>(context.getSession(), context.getRealm(), context.getAuthenticationSession()).getCode();
|
||||
String clientId = context.getAuthenticationSession().getClient().getClientId();
|
||||
Response response = Response.seeOther(
|
||||
Urls.identityProviderAuthnRequest(context.getUriInfo().getBaseUri(), providerId, context.getRealm().getName(), accessCode))
|
||||
Urls.identityProviderAuthnRequest(context.getUriInfo().getBaseUri(), providerId, context.getRealm().getName(), accessCode, clientId))
|
||||
.build();
|
||||
|
||||
LOG.debugf("Redirecting to %s", providerId);
|
||||
|
|
|
@ -36,7 +36,6 @@ import java.util.List;
|
|||
*/
|
||||
public class RegistrationPage implements FormAuthenticator, FormAuthenticatorFactory {
|
||||
|
||||
public static final String EXECUTION = "execution";
|
||||
public static final String FIELD_PASSWORD_CONFIRM = "password-confirm";
|
||||
public static final String FIELD_PASSWORD = "password";
|
||||
public static final String FIELD_EMAIL = "email";
|
||||
|
|
|
@ -155,7 +155,7 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
|
|||
protected UriBuilder createAuthorizationUrl(AuthenticationRequest request) {
|
||||
return UriBuilder.fromUri(getConfig().getAuthorizationUrl())
|
||||
.queryParam(OAUTH2_PARAMETER_SCOPE, getConfig().getDefaultScope())
|
||||
.queryParam(OAUTH2_PARAMETER_STATE, request.getState())
|
||||
.queryParam(OAUTH2_PARAMETER_STATE, request.getState().getEncodedState())
|
||||
.queryParam(OAUTH2_PARAMETER_RESPONSE_TYPE, "code")
|
||||
.queryParam(OAUTH2_PARAMETER_CLIENT_ID, getConfig().getClientId())
|
||||
.queryParam(OAUTH2_PARAMETER_REDIRECT_URI, request.getRedirectUri());
|
||||
|
|
|
@ -100,7 +100,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
|||
.protocolBinding(protocolBinding)
|
||||
.nameIdPolicy(SAML2NameIDPolicyBuilder.format(nameIDPolicyFormat));
|
||||
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder()
|
||||
.relayState(request.getState());
|
||||
.relayState(request.getState().getEncodedState());
|
||||
boolean postBinding = getConfig().isPostBindingAuthnRequest();
|
||||
|
||||
if (getConfig().isWantAuthnRequestsSigned()) {
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
package org.keycloak.forms.login.freemarker;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.authentication.requiredactions.util.UpdateProfileContext;
|
||||
import org.keycloak.authentication.requiredactions.util.UserUpdateProfileContext;
|
||||
|
@ -40,7 +39,6 @@ import org.keycloak.models.*;
|
|||
import org.keycloak.models.utils.FormMessage;
|
||||
import org.keycloak.services.Urls;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||
import org.keycloak.theme.BrowserSecurityHeaderSetup;
|
||||
import org.keycloak.theme.FreeMarkerException;
|
||||
import org.keycloak.theme.FreeMarkerUtil;
|
||||
|
@ -75,7 +73,6 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
private List<RoleModel> realmRolesRequested;
|
||||
private MultivaluedMap<String, RoleModel> resourceRolesRequested;
|
||||
private List<ProtocolMapperModel> protocolMappersRequested;
|
||||
private MultivaluedMap<String, String> queryParams;
|
||||
private Map<String, String> httpResponseHeaders = new HashMap<String, String>();
|
||||
private String accessRequestMessage;
|
||||
private URI actionUri;
|
||||
|
@ -146,8 +143,6 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
ClientModel client = session.getContext().getClient();
|
||||
UriInfo uriInfo = session.getContext().getUri();
|
||||
|
||||
MultivaluedMap<String, String> queryParameterMap = queryParams != null ? queryParams : new MultivaluedMapImpl<String, String>();
|
||||
|
||||
String requestURI = uriInfo.getBaseUri().getPath();
|
||||
UriBuilder uriBuilder = UriBuilder.fromUri(requestURI);
|
||||
if (page == LoginFormsPages.OAUTH_GRANT) {
|
||||
|
@ -155,19 +150,17 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
uriBuilder.replaceQuery(null);
|
||||
}
|
||||
|
||||
if (client != null) {
|
||||
uriBuilder.queryParam(Constants.CLIENT_ID, client.getClientId());
|
||||
}
|
||||
|
||||
URI baseUri = uriBuilder.build();
|
||||
|
||||
if (accessCode != null) {
|
||||
uriBuilder.queryParam(OAuth2Constants.CODE, accessCode);
|
||||
}
|
||||
URI baseUriWithCode = uriBuilder.build();
|
||||
|
||||
for (String k : queryParameterMap.keySet()) {
|
||||
|
||||
Object[] objects = queryParameterMap.get(k).toArray();
|
||||
if (objects.length == 1 && objects[0] == null) continue; //
|
||||
uriBuilder.replaceQueryParam(k, objects);
|
||||
}
|
||||
URI baseUriWithCodeAndClientId = uriBuilder.build();
|
||||
|
||||
ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
|
||||
Theme theme;
|
||||
|
@ -220,7 +213,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
|
||||
List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
|
||||
identityProviders = LoginFormsUtil.filterIdentityProviders(identityProviders, session, realm, attributes, formData);
|
||||
attributes.put("social", new IdentityProviderBean(realm, session, identityProviders, baseUriWithCode));
|
||||
attributes.put("social", new IdentityProviderBean(realm, session, identityProviders, baseUriWithCodeAndClientId));
|
||||
|
||||
attributes.put("url", new UrlBean(realm, theme, baseUri, this.actionUri));
|
||||
|
||||
|
@ -301,16 +294,11 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
ClientModel client = session.getContext().getClient();
|
||||
UriInfo uriInfo = session.getContext().getUri();
|
||||
|
||||
MultivaluedMap<String, String> queryParameterMap = queryParams != null ? queryParams : new MultivaluedMapImpl<String, String>();
|
||||
|
||||
String requestURI = uriInfo.getBaseUri().getPath();
|
||||
UriBuilder uriBuilder = UriBuilder.fromUri(requestURI);
|
||||
|
||||
for (String k : queryParameterMap.keySet()) {
|
||||
|
||||
Object[] objects = queryParameterMap.get(k).toArray();
|
||||
if (objects.length == 1 && objects[0] == null) continue; //
|
||||
uriBuilder.replaceQueryParam(k, objects);
|
||||
if (client != null) {
|
||||
uriBuilder.queryParam(Constants.CLIENT_ID, client.getClientId());
|
||||
}
|
||||
|
||||
URI baseUri = uriBuilder.build();
|
||||
|
@ -318,6 +306,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
if (accessCode != null) {
|
||||
uriBuilder.queryParam(OAuth2Constants.CODE, accessCode);
|
||||
}
|
||||
|
||||
URI baseUriWithCode = uriBuilder.build();
|
||||
|
||||
ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
|
||||
|
|
|
@ -18,6 +18,7 @@ package org.keycloak.services;
|
|||
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.common.Version;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
||||
import org.keycloak.services.resources.AccountService;
|
||||
|
@ -73,13 +74,16 @@ public class Urls {
|
|||
.build(realmName, providerId);
|
||||
}
|
||||
|
||||
public static URI identityProviderAuthnRequest(URI baseUri, String providerId, String realmName, String accessCode) {
|
||||
public static URI identityProviderAuthnRequest(URI baseUri, String providerId, String realmName, String accessCode, String clientId) {
|
||||
UriBuilder uriBuilder = realmBase(baseUri).path(RealmsResource.class, "getBrokerService")
|
||||
.path(IdentityBrokerService.class, "performLogin");
|
||||
|
||||
if (accessCode != null) {
|
||||
uriBuilder.replaceQueryParam(OAuth2Constants.CODE, accessCode);
|
||||
}
|
||||
if (clientId != null) {
|
||||
uriBuilder.replaceQueryParam(Constants.CLIENT_ID, clientId);
|
||||
}
|
||||
|
||||
return uriBuilder.build(realmName, providerId);
|
||||
}
|
||||
|
@ -99,20 +103,22 @@ public class Urls {
|
|||
}
|
||||
|
||||
public static URI identityProviderAuthnRequest(URI baseURI, String providerId, String realmName) {
|
||||
return identityProviderAuthnRequest(baseURI, providerId, realmName, null);
|
||||
return identityProviderAuthnRequest(baseURI, providerId, realmName, null, null);
|
||||
}
|
||||
|
||||
public static URI identityProviderAfterFirstBrokerLogin(URI baseUri, String realmName, String accessCode) {
|
||||
public static URI identityProviderAfterFirstBrokerLogin(URI baseUri, String realmName, String accessCode, String clientId) {
|
||||
return realmBase(baseUri).path(RealmsResource.class, "getBrokerService")
|
||||
.path(IdentityBrokerService.class, "afterFirstBrokerLogin")
|
||||
.replaceQueryParam(OAuth2Constants.CODE, accessCode)
|
||||
.replaceQueryParam(Constants.CLIENT_ID, clientId)
|
||||
.build(realmName);
|
||||
}
|
||||
|
||||
public static URI identityProviderAfterPostBrokerLogin(URI baseUri, String realmName, String accessCode) {
|
||||
public static URI identityProviderAfterPostBrokerLogin(URI baseUri, String realmName, String accessCode, String clientId) {
|
||||
return realmBase(baseUri).path(RealmsResource.class, "getBrokerService")
|
||||
.path(IdentityBrokerService.class, "afterPostBrokerLoginFlow")
|
||||
.replaceQueryParam(OAuth2Constants.CODE, accessCode)
|
||||
.replaceQueryParam(Constants.CLIENT_ID, clientId)
|
||||
.build(realmName);
|
||||
}
|
||||
|
||||
|
|
|
@ -78,9 +78,6 @@ public class AuthenticationManager {
|
|||
public static final String END_AFTER_REQUIRED_ACTIONS = "END_AFTER_REQUIRED_ACTIONS";
|
||||
public static final String INVALIDATE_ACTION_TOKEN = "INVALIDATE_ACTION_TOKEN";
|
||||
|
||||
// Last authenticated client in userSession.
|
||||
public static final String LAST_AUTHENTICATED_CLIENT = "LAST_AUTHENTICATED_CLIENT";
|
||||
|
||||
// userSession note with authTime (time when authentication flow including requiredActions was finished)
|
||||
public static final String AUTH_TIME = "AUTH_TIME";
|
||||
// clientSession note with flag that clientSession was authenticated through SSO cookie
|
||||
|
@ -95,7 +92,6 @@ public class AuthenticationManager {
|
|||
public static final String KEYCLOAK_SESSION_COOKIE = "KEYCLOAK_SESSION";
|
||||
public static final String KEYCLOAK_REMEMBER_ME = "KEYCLOAK_REMEMBER_ME";
|
||||
public static final String KEYCLOAK_LOGOUT_PROTOCOL = "KEYCLOAK_LOGOUT_PROTOCOL";
|
||||
public static final String CURRENT_REQUIRED_ACTION = "CURRENT_REQUIRED_ACTION";
|
||||
|
||||
public static boolean isSessionValid(RealmModel realm, UserSessionModel userSession) {
|
||||
if (userSession == null) {
|
||||
|
@ -463,8 +459,6 @@ public class AuthenticationManager {
|
|||
userSession.setNote(AUTH_TIME, String.valueOf(authTime));
|
||||
}
|
||||
|
||||
userSession.setNote(LAST_AUTHENTICATED_CLIENT, clientSession.getClient().getId());
|
||||
|
||||
return protocol.authenticated(userSession, clientSession);
|
||||
|
||||
}
|
||||
|
@ -496,9 +490,11 @@ public class AuthenticationManager {
|
|||
.path(LoginActionsService.REQUIRED_ACTION);
|
||||
|
||||
if (requiredAction != null) {
|
||||
uriBuilder.queryParam("execution", requiredAction);
|
||||
uriBuilder.queryParam(Constants.EXECUTION, requiredAction);
|
||||
}
|
||||
|
||||
uriBuilder.queryParam(Constants.CLIENT_ID, authSession.getClient().getClientId());
|
||||
|
||||
URI redirect = uriBuilder.build(realm.getName());
|
||||
return Response.status(302).location(redirect).build();
|
||||
|
||||
|
@ -526,7 +522,7 @@ public class AuthenticationManager {
|
|||
}
|
||||
|
||||
} else {
|
||||
infoPage.setAttribute("skipLink", true);
|
||||
infoPage.setAttribute(Constants.SKIP_LINK, true);
|
||||
}
|
||||
Response response = infoPage
|
||||
.createInfoPage();
|
||||
|
|
|
@ -30,6 +30,7 @@ import org.keycloak.broker.provider.IdentityBrokerException;
|
|||
import org.keycloak.broker.provider.IdentityProvider;
|
||||
import org.keycloak.broker.provider.IdentityProviderFactory;
|
||||
import org.keycloak.broker.provider.IdentityProviderMapper;
|
||||
import org.keycloak.broker.provider.util.IdentityBrokerState;
|
||||
import org.keycloak.broker.saml.SAMLEndpoint;
|
||||
import org.keycloak.broker.social.SocialIdentityProvider;
|
||||
import org.keycloak.common.ClientConnection;
|
||||
|
@ -338,14 +339,14 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
|||
|
||||
@POST
|
||||
@Path("/{provider_id}/login")
|
||||
public Response performPostLogin(@PathParam("provider_id") String providerId, @QueryParam("code") String code) {
|
||||
return performLogin(providerId, code);
|
||||
public Response performPostLogin(@PathParam("provider_id") String providerId, @QueryParam("code") String code, @QueryParam("client_id") String clientId) {
|
||||
return performLogin(providerId, code, clientId);
|
||||
}
|
||||
|
||||
@GET
|
||||
@NoCache
|
||||
@Path("/{provider_id}/login")
|
||||
public Response performLogin(@PathParam("provider_id") String providerId, @QueryParam("code") String code) {
|
||||
public Response performLogin(@PathParam("provider_id") String providerId, @QueryParam("code") String code, @QueryParam("client_id") String clientId) {
|
||||
this.event.detail(Details.IDENTITY_PROVIDER, providerId);
|
||||
|
||||
if (isDebugEnabled()) {
|
||||
|
@ -353,7 +354,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
|||
}
|
||||
|
||||
try {
|
||||
ParsedCodeContext parsedCode = parseClientSessionCode(code);
|
||||
ParsedCodeContext parsedCode = parseSessionCode(code, clientId);
|
||||
if (parsedCode.response != null) {
|
||||
return parsedCode.response;
|
||||
}
|
||||
|
@ -479,7 +480,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
|||
if (context.getContextData().get(SAMLEndpoint.SAML_IDP_INITIATED_CLIENT_ID) != null) {
|
||||
parsedCode = samlIdpInitiatedSSO((String) context.getContextData().get(SAMLEndpoint.SAML_IDP_INITIATED_CLIENT_ID));
|
||||
} else {
|
||||
parsedCode = parseClientSessionCode(context.getCode());
|
||||
parsedCode = parseEncodedSessionCode(context.getCode());
|
||||
}
|
||||
if (parsedCode.response != null) {
|
||||
return parsedCode.response;
|
||||
|
@ -549,6 +550,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
|||
ctx.saveToAuthenticationSession(authenticationSession, AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE);
|
||||
|
||||
URI redirect = LoginActionsService.firstBrokerLoginProcessor(uriInfo)
|
||||
.queryParam(Constants.CLIENT_ID, authenticationSession.getClient().getClientId())
|
||||
.build(realmModel.getName());
|
||||
return Response.status(302).location(redirect).build();
|
||||
|
||||
|
@ -584,8 +586,8 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
|||
@GET
|
||||
@NoCache
|
||||
@Path("/after-first-broker-login")
|
||||
public Response afterFirstBrokerLogin(@QueryParam("code") String code) {
|
||||
ParsedCodeContext parsedCode = parseClientSessionCode(code);
|
||||
public Response afterFirstBrokerLogin(@QueryParam("code") String code, @QueryParam("client_id") String clientId) {
|
||||
ParsedCodeContext parsedCode = parseSessionCode(code, clientId);
|
||||
if (parsedCode.response != null) {
|
||||
return parsedCode.response;
|
||||
}
|
||||
|
@ -701,6 +703,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
|||
authSession.setAuthNote(PostBrokerLoginConstants.PBL_AFTER_FIRST_BROKER_LOGIN, String.valueOf(wasFirstBrokerLogin));
|
||||
|
||||
URI redirect = LoginActionsService.postBrokerLoginProcessor(uriInfo)
|
||||
.queryParam(Constants.CLIENT_ID, authSession.getClient().getClientId())
|
||||
.build(realmModel.getName());
|
||||
return Response.status(302).location(redirect).build();
|
||||
}
|
||||
|
@ -711,8 +714,8 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
|||
@GET
|
||||
@NoCache
|
||||
@Path("/after-post-broker-login")
|
||||
public Response afterPostBrokerLoginFlow(@QueryParam("code") String code) {
|
||||
ParsedCodeContext parsedCode = parseClientSessionCode(code);
|
||||
public Response afterPostBrokerLoginFlow(@QueryParam("code") String code, @QueryParam("client_id") String clientId) {
|
||||
ParsedCodeContext parsedCode = parseSessionCode(code, clientId);
|
||||
if (parsedCode.response != null) {
|
||||
return parsedCode.response;
|
||||
}
|
||||
|
@ -804,7 +807,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
|||
|
||||
@Override
|
||||
public Response cancelled(String code) {
|
||||
ParsedCodeContext parsedCode = parseClientSessionCode(code);
|
||||
ParsedCodeContext parsedCode = parseEncodedSessionCode(code);
|
||||
if (parsedCode.response != null) {
|
||||
return parsedCode.response;
|
||||
}
|
||||
|
@ -820,7 +823,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
|||
|
||||
@Override
|
||||
public Response error(String code, String message) {
|
||||
ParsedCodeContext parsedCode = parseClientSessionCode(code);
|
||||
ParsedCodeContext parsedCode = parseEncodedSessionCode(code);
|
||||
if (parsedCode.response != null) {
|
||||
return parsedCode.response;
|
||||
}
|
||||
|
@ -960,14 +963,21 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
|||
}
|
||||
}
|
||||
|
||||
private ParsedCodeContext parseClientSessionCode(String code) {
|
||||
if (code == null) {
|
||||
logger.debugf("Invalid request. Authorization code was null");
|
||||
private ParsedCodeContext parseEncodedSessionCode(String encodedCode) {
|
||||
IdentityBrokerState state = IdentityBrokerState.encoded(encodedCode);
|
||||
String code = state.getDecodedState();
|
||||
String clientId = state.getClientId();
|
||||
return parseSessionCode(code, clientId);
|
||||
}
|
||||
|
||||
private ParsedCodeContext parseSessionCode(String code, String clientId) {
|
||||
if (code == null || clientId == null) {
|
||||
logger.debugf("Invalid request. Authorization code or clientId was null. Code=" + code + ", clientId=" + clientId);
|
||||
Response staleCodeError = redirectToErrorPage(Messages.INVALID_REQUEST);
|
||||
return ParsedCodeContext.response(staleCodeError);
|
||||
}
|
||||
|
||||
SessionCodeChecks checks = new SessionCodeChecks(realmModel, uriInfo, clientConnection, session, event, code, null, LoginActionsService.AUTHENTICATE_PATH);
|
||||
SessionCodeChecks checks = new SessionCodeChecks(realmModel, uriInfo, clientConnection, session, event, code, null, clientId, LoginActionsService.AUTHENTICATE_PATH);
|
||||
checks.initialVerify();
|
||||
if (!checks.verifyActiveAndValidAction(AuthenticationSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
|
||||
|
||||
|
@ -1041,14 +1051,15 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
|||
|
||||
private AuthenticationRequest createAuthenticationRequest(String providerId, ClientSessionCode<AuthenticationSessionModel> clientSessionCode) {
|
||||
AuthenticationSessionModel authSession = null;
|
||||
String relayState = null;
|
||||
IdentityBrokerState encodedState = null;
|
||||
|
||||
if (clientSessionCode != null) {
|
||||
authSession = clientSessionCode.getClientSession();
|
||||
relayState = clientSessionCode.getCode();
|
||||
String relayState = clientSessionCode.getCode();
|
||||
encodedState = IdentityBrokerState.decoded(relayState, authSession.getClient().getClientId());
|
||||
}
|
||||
|
||||
return new AuthenticationRequest(this.session, this.realmModel, authSession, this.request, this.uriInfo, relayState, getRedirectUri(providerId));
|
||||
return new AuthenticationRequest(this.session, this.realmModel, authSession, this.request, this.uriInfo, encodedState, getRedirectUri(providerId));
|
||||
}
|
||||
|
||||
private String getRedirectUri(String providerId) {
|
||||
|
|
|
@ -179,16 +179,16 @@ public class LoginActionsService {
|
|||
}
|
||||
}
|
||||
|
||||
private SessionCodeChecks checksForCode(String code, String execution, String flowPath) {
|
||||
SessionCodeChecks res = new SessionCodeChecks(realm, uriInfo, clientConnection, session, event, code, execution, flowPath);
|
||||
private SessionCodeChecks checksForCode(String code, String execution, String clientId, String flowPath) {
|
||||
SessionCodeChecks res = new SessionCodeChecks(realm, uriInfo, clientConnection, session, event, code, execution, clientId, flowPath);
|
||||
res.initialVerify();
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
protected URI getLastExecutionUrl(String flowPath, String executionId) {
|
||||
protected URI getLastExecutionUrl(String flowPath, String executionId, String clientId) {
|
||||
return new AuthenticationFlowURLHelper(session, realm, uriInfo)
|
||||
.getLastExecutionUrl(flowPath, executionId);
|
||||
.getLastExecutionUrl(flowPath, executionId, clientId);
|
||||
}
|
||||
|
||||
|
||||
|
@ -199,9 +199,9 @@ public class LoginActionsService {
|
|||
*/
|
||||
@Path(RESTART_PATH)
|
||||
@GET
|
||||
public Response restartSession() {
|
||||
public Response restartSession(@QueryParam("client_id") String clientId) {
|
||||
event.event(EventType.RESTART_AUTHENTICATION);
|
||||
SessionCodeChecks checks = new SessionCodeChecks(realm, uriInfo, clientConnection, session, event, null, null, null);
|
||||
SessionCodeChecks checks = new SessionCodeChecks(realm, uriInfo, clientConnection, session, event, null, null, clientId, null);
|
||||
|
||||
AuthenticationSessionModel authSession = checks.initialVerifyAuthSession();
|
||||
if (authSession == null) {
|
||||
|
@ -215,7 +215,7 @@ public class LoginActionsService {
|
|||
|
||||
AuthenticationProcessor.resetFlow(authSession, flowPath);
|
||||
|
||||
URI redirectUri = getLastExecutionUrl(flowPath, null);
|
||||
URI redirectUri = getLastExecutionUrl(flowPath, null, authSession.getClient().getClientId());
|
||||
logger.debugf("Flow restart requested. Redirecting to %s", redirectUri);
|
||||
return Response.status(Response.Status.FOUND).location(redirectUri).build();
|
||||
}
|
||||
|
@ -230,10 +230,11 @@ public class LoginActionsService {
|
|||
@Path(AUTHENTICATE_PATH)
|
||||
@GET
|
||||
public Response authenticate(@QueryParam("code") String code,
|
||||
@QueryParam("execution") String execution) {
|
||||
@QueryParam("execution") String execution,
|
||||
@QueryParam("client_id") String clientId) {
|
||||
event.event(EventType.LOGIN);
|
||||
|
||||
SessionCodeChecks checks = checksForCode(code, execution, AUTHENTICATE_PATH);
|
||||
SessionCodeChecks checks = checksForCode(code, execution, clientId, AUTHENTICATE_PATH);
|
||||
if (!checks.verifyActiveAndValidAction(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
|
||||
return checks.getResponse();
|
||||
}
|
||||
|
@ -298,22 +299,24 @@ public class LoginActionsService {
|
|||
@Path(AUTHENTICATE_PATH)
|
||||
@POST
|
||||
public Response authenticateForm(@QueryParam("code") String code,
|
||||
@QueryParam("execution") String execution) {
|
||||
return authenticate(code, execution);
|
||||
@QueryParam("execution") String execution,
|
||||
@QueryParam("client_id") String clientId) {
|
||||
return authenticate(code, execution, clientId);
|
||||
}
|
||||
|
||||
@Path(RESET_CREDENTIALS_PATH)
|
||||
@POST
|
||||
public Response resetCredentialsPOST(@QueryParam("code") String code,
|
||||
@QueryParam("execution") String execution,
|
||||
@QueryParam("client_id") String clientId,
|
||||
@QueryParam(Constants.KEY) String key) {
|
||||
if (key != null) {
|
||||
return handleActionToken(key, execution);
|
||||
return handleActionToken(key, execution, clientId);
|
||||
}
|
||||
|
||||
event.event(EventType.RESET_PASSWORD);
|
||||
|
||||
return resetCredentials(code, execution);
|
||||
return resetCredentials(code, execution, clientId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -327,7 +330,8 @@ public class LoginActionsService {
|
|||
@Path(RESET_CREDENTIALS_PATH)
|
||||
@GET
|
||||
public Response resetCredentialsGET(@QueryParam("code") String code,
|
||||
@QueryParam("execution") String execution) {
|
||||
@QueryParam("execution") String execution,
|
||||
@QueryParam("client_id") String clientId) {
|
||||
AuthenticationSessionModel authSession = new AuthenticationSessionManager(session).getCurrentAuthenticationSession(realm);
|
||||
|
||||
// we allow applications to link to reset credentials without going through OAuth or SAML handshakes
|
||||
|
@ -343,7 +347,7 @@ public class LoginActionsService {
|
|||
}
|
||||
|
||||
event.event(EventType.RESET_PASSWORD);
|
||||
return resetCredentials(code, execution);
|
||||
return resetCredentials(code, execution, clientId);
|
||||
}
|
||||
|
||||
AuthenticationSessionModel createAuthenticationSessionForClient()
|
||||
|
@ -370,8 +374,8 @@ public class LoginActionsService {
|
|||
* @param execution
|
||||
* @return
|
||||
*/
|
||||
protected Response resetCredentials(String code, String execution) {
|
||||
SessionCodeChecks checks = checksForCode(code, execution, RESET_CREDENTIALS_PATH);
|
||||
protected Response resetCredentials(String code, String execution, String clientId) {
|
||||
SessionCodeChecks checks = checksForCode(code, execution, clientId, RESET_CREDENTIALS_PATH);
|
||||
if (!checks.verifyActiveAndValidAction(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.USER)) {
|
||||
return checks.getResponse();
|
||||
}
|
||||
|
@ -397,11 +401,12 @@ public class LoginActionsService {
|
|||
@Path("action-token")
|
||||
@GET
|
||||
public Response executeActionToken(@QueryParam("key") String key,
|
||||
@QueryParam("execution") String execution) {
|
||||
return handleActionToken(key, execution);
|
||||
@QueryParam("execution") String execution,
|
||||
@QueryParam("client_id") String clientId) {
|
||||
return handleActionToken(key, execution, clientId);
|
||||
}
|
||||
|
||||
protected <T extends DefaultActionToken> Response handleActionToken(String tokenString, String execution) {
|
||||
protected <T extends DefaultActionToken> Response handleActionToken(String tokenString, String execution, String clientId) {
|
||||
T token;
|
||||
ActionTokenHandler<T> handler;
|
||||
ActionTokenContext<T> tokenContext;
|
||||
|
@ -411,6 +416,15 @@ public class LoginActionsService {
|
|||
|
||||
event.event(EventType.EXECUTE_ACTION_TOKEN);
|
||||
|
||||
// Setup client, so error page will contain "back to application" link
|
||||
ClientModel client = null;
|
||||
if (clientId != null) {
|
||||
client = realm.getClientByClientId(clientId);
|
||||
}
|
||||
if (client != null) {
|
||||
session.getContext().setClient(client);
|
||||
}
|
||||
|
||||
// First resolve action token handler
|
||||
try {
|
||||
if (tokenString == null) {
|
||||
|
@ -570,8 +584,9 @@ public class LoginActionsService {
|
|||
@Path(REGISTRATION_PATH)
|
||||
@GET
|
||||
public Response registerPage(@QueryParam("code") String code,
|
||||
@QueryParam("execution") String execution) {
|
||||
return registerRequest(code, execution, false);
|
||||
@QueryParam("execution") String execution,
|
||||
@QueryParam("client_id") String clientId) {
|
||||
return registerRequest(code, execution, clientId, false);
|
||||
}
|
||||
|
||||
|
||||
|
@ -584,19 +599,20 @@ public class LoginActionsService {
|
|||
@Path(REGISTRATION_PATH)
|
||||
@POST
|
||||
public Response processRegister(@QueryParam("code") String code,
|
||||
@QueryParam("execution") String execution) {
|
||||
return registerRequest(code, execution, true);
|
||||
@QueryParam("execution") String execution,
|
||||
@QueryParam("client_id") String clientId) {
|
||||
return registerRequest(code, execution, clientId, true);
|
||||
}
|
||||
|
||||
|
||||
private Response registerRequest(String code, String execution, boolean isPostRequest) {
|
||||
private Response registerRequest(String code, String execution, String clientId, boolean isPostRequest) {
|
||||
event.event(EventType.REGISTER);
|
||||
if (!realm.isRegistrationAllowed()) {
|
||||
event.error(Errors.REGISTRATION_DISABLED);
|
||||
return ErrorPage.error(session, Messages.REGISTRATION_NOT_ALLOWED);
|
||||
}
|
||||
|
||||
SessionCodeChecks checks = checksForCode(code, execution, REGISTRATION_PATH);
|
||||
SessionCodeChecks checks = checksForCode(code, execution, clientId, REGISTRATION_PATH);
|
||||
if (!checks.verifyActiveAndValidAction(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
|
||||
return checks.getResponse();
|
||||
}
|
||||
|
@ -612,39 +628,43 @@ public class LoginActionsService {
|
|||
@Path(FIRST_BROKER_LOGIN_PATH)
|
||||
@GET
|
||||
public Response firstBrokerLoginGet(@QueryParam("code") String code,
|
||||
@QueryParam("execution") String execution) {
|
||||
return brokerLoginFlow(code, execution, FIRST_BROKER_LOGIN_PATH);
|
||||
@QueryParam("execution") String execution,
|
||||
@QueryParam("client_id") String clientId) {
|
||||
return brokerLoginFlow(code, execution, clientId, FIRST_BROKER_LOGIN_PATH);
|
||||
}
|
||||
|
||||
@Path(FIRST_BROKER_LOGIN_PATH)
|
||||
@POST
|
||||
public Response firstBrokerLoginPost(@QueryParam("code") String code,
|
||||
@QueryParam("execution") String execution) {
|
||||
return brokerLoginFlow(code, execution, FIRST_BROKER_LOGIN_PATH);
|
||||
@QueryParam("execution") String execution,
|
||||
@QueryParam("client_id") String clientId) {
|
||||
return brokerLoginFlow(code, execution, clientId, FIRST_BROKER_LOGIN_PATH);
|
||||
}
|
||||
|
||||
@Path(POST_BROKER_LOGIN_PATH)
|
||||
@GET
|
||||
public Response postBrokerLoginGet(@QueryParam("code") String code,
|
||||
@QueryParam("execution") String execution) {
|
||||
return brokerLoginFlow(code, execution, POST_BROKER_LOGIN_PATH);
|
||||
@QueryParam("execution") String execution,
|
||||
@QueryParam("client_id") String clientId) {
|
||||
return brokerLoginFlow(code, execution, clientId, POST_BROKER_LOGIN_PATH);
|
||||
}
|
||||
|
||||
@Path(POST_BROKER_LOGIN_PATH)
|
||||
@POST
|
||||
public Response postBrokerLoginPost(@QueryParam("code") String code,
|
||||
@QueryParam("execution") String execution) {
|
||||
return brokerLoginFlow(code, execution, POST_BROKER_LOGIN_PATH);
|
||||
@QueryParam("execution") String execution,
|
||||
@QueryParam("client_id") String clientId) {
|
||||
return brokerLoginFlow(code, execution, clientId, POST_BROKER_LOGIN_PATH);
|
||||
}
|
||||
|
||||
|
||||
protected Response brokerLoginFlow(String code, String execution, String flowPath) {
|
||||
protected Response brokerLoginFlow(String code, String execution, String clientId, String flowPath) {
|
||||
boolean firstBrokerLogin = flowPath.equals(FIRST_BROKER_LOGIN_PATH);
|
||||
|
||||
EventType eventType = firstBrokerLogin ? EventType.IDENTITY_PROVIDER_FIRST_LOGIN : EventType.IDENTITY_PROVIDER_POST_LOGIN;
|
||||
event.event(eventType);
|
||||
|
||||
SessionCodeChecks checks = checksForCode(code, execution, flowPath);
|
||||
SessionCodeChecks checks = checksForCode(code, execution, clientId, flowPath);
|
||||
if (!checks.verifyActiveAndValidAction(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
|
||||
return checks.getResponse();
|
||||
}
|
||||
|
@ -702,8 +722,9 @@ public class LoginActionsService {
|
|||
ClientSessionCode<AuthenticationSessionModel> accessCode = new ClientSessionCode<>(session, realm, authSession);
|
||||
authSession.setTimestamp(Time.currentTime());
|
||||
|
||||
URI redirect = firstBrokerLogin ? Urls.identityProviderAfterFirstBrokerLogin(uriInfo.getBaseUri(), realm.getName(), accessCode.getCode()) :
|
||||
Urls.identityProviderAfterPostBrokerLogin(uriInfo.getBaseUri(), realm.getName(), accessCode.getCode()) ;
|
||||
String clientId = authSession.getClient().getClientId();
|
||||
URI redirect = firstBrokerLogin ? Urls.identityProviderAfterFirstBrokerLogin(uriInfo.getBaseUri(), realm.getName(), accessCode.getCode(), clientId) :
|
||||
Urls.identityProviderAfterPostBrokerLogin(uriInfo.getBaseUri(), realm.getName(), accessCode.getCode(), clientId) ;
|
||||
logger.debugf("Redirecting to '%s' ", redirect);
|
||||
|
||||
return Response.status(302).location(redirect).build();
|
||||
|
@ -722,7 +743,8 @@ public class LoginActionsService {
|
|||
public Response processConsent(final MultivaluedMap<String, String> formData) {
|
||||
event.event(EventType.LOGIN);
|
||||
String code = formData.getFirst("code");
|
||||
SessionCodeChecks checks = checksForCode(code, null, REQUIRED_ACTION);
|
||||
String clientId = uriInfo.getQueryParameters().getFirst(Constants.CLIENT_ID);
|
||||
SessionCodeChecks checks = checksForCode(code, null, clientId, REQUIRED_ACTION);
|
||||
if (!checks.verifyRequiredAction(ClientSessionModel.Action.OAUTH_GRANT.name())) {
|
||||
return checks.getResponse();
|
||||
}
|
||||
|
@ -811,21 +833,23 @@ public class LoginActionsService {
|
|||
@Path(REQUIRED_ACTION)
|
||||
@POST
|
||||
public Response requiredActionPOST(@QueryParam("code") final String code,
|
||||
@QueryParam("execution") String action) {
|
||||
return processRequireAction(code, action);
|
||||
@QueryParam("execution") String action,
|
||||
@QueryParam("client_id") String clientId) {
|
||||
return processRequireAction(code, action, clientId);
|
||||
}
|
||||
|
||||
@Path(REQUIRED_ACTION)
|
||||
@GET
|
||||
public Response requiredActionGET(@QueryParam("code") final String code,
|
||||
@QueryParam("execution") String action) {
|
||||
return processRequireAction(code, action);
|
||||
@QueryParam("execution") String action,
|
||||
@QueryParam("client_id") String clientId) {
|
||||
return processRequireAction(code, action, clientId);
|
||||
}
|
||||
|
||||
private Response processRequireAction(final String code, String action) {
|
||||
private Response processRequireAction(final String code, String action, String clientId) {
|
||||
event.event(EventType.CUSTOM_REQUIRED_ACTION);
|
||||
|
||||
SessionCodeChecks checks = checksForCode(code, action, REQUIRED_ACTION);
|
||||
SessionCodeChecks checks = checksForCode(code, action, clientId, REQUIRED_ACTION);
|
||||
if (!checks.verifyRequiredAction(action)) {
|
||||
return checks.getResponse();
|
||||
}
|
||||
|
|
|
@ -120,16 +120,8 @@ public class LoginActionsServiceChecks {
|
|||
LoginFormsProvider loginForm = context.getSession().getProvider(LoginFormsProvider.class)
|
||||
.setSuccess(Messages.ALREADY_LOGGED_IN);
|
||||
|
||||
ClientModel client = null;
|
||||
String lastClientUuid = userSession.getNote(AuthenticationManager.LAST_AUTHENTICATED_CLIENT);
|
||||
if (lastClientUuid != null) {
|
||||
client = context.getRealm().getClientById(lastClientUuid);
|
||||
}
|
||||
|
||||
if (client != null) {
|
||||
context.getSession().getContext().setClient(client);
|
||||
} else {
|
||||
loginForm.setAttribute("skipLink", true);
|
||||
if (context.getSession().getContext().getClient() == null) {
|
||||
loginForm.setAttribute(Constants.SKIP_LINK, true);
|
||||
}
|
||||
|
||||
throw new LoginActionsServiceException(loginForm.createInfoPage());
|
||||
|
|
|
@ -33,6 +33,7 @@ import org.keycloak.events.EventBuilder;
|
|||
import org.keycloak.forms.login.LoginFormsProvider;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
|
@ -66,10 +67,11 @@ public class SessionCodeChecks {
|
|||
|
||||
private final String code;
|
||||
private final String execution;
|
||||
private final String clientId;
|
||||
private final String flowPath;
|
||||
|
||||
|
||||
public SessionCodeChecks(RealmModel realm, UriInfo uriInfo, ClientConnection clientConnection, KeycloakSession session, EventBuilder event, String code, String execution, String flowPath) {
|
||||
public SessionCodeChecks(RealmModel realm, UriInfo uriInfo, ClientConnection clientConnection, KeycloakSession session, EventBuilder event, String code, String execution, String clientId, String flowPath) {
|
||||
this.realm = realm;
|
||||
this.uriInfo = uriInfo;
|
||||
this.clientConnection = clientConnection;
|
||||
|
@ -78,6 +80,7 @@ public class SessionCodeChecks {
|
|||
|
||||
this.code = code;
|
||||
this.execution = execution;
|
||||
this.clientId = clientId;
|
||||
this.flowPath = flowPath;
|
||||
}
|
||||
|
||||
|
@ -134,6 +137,16 @@ public class SessionCodeChecks {
|
|||
return authSession;
|
||||
}
|
||||
|
||||
// Setup client to be shown on error/info page based on "client_id" parameter
|
||||
logger.debugf("Will use client '%s' in back-to-application link", clientId);
|
||||
ClientModel client = null;
|
||||
if (clientId != null) {
|
||||
client = realm.getClientByClientId(clientId);
|
||||
}
|
||||
if (client != null) {
|
||||
session.getContext().setClient(client);
|
||||
}
|
||||
|
||||
// See if we are already authenticated and userSession with same ID exists.
|
||||
String sessionId = new AuthenticationSessionManager(session).getCurrentAuthenticationSessionId(realm);
|
||||
if (sessionId != null) {
|
||||
|
@ -143,16 +156,8 @@ public class SessionCodeChecks {
|
|||
LoginFormsProvider loginForm = session.getProvider(LoginFormsProvider.class)
|
||||
.setSuccess(Messages.ALREADY_LOGGED_IN);
|
||||
|
||||
ClientModel client = null;
|
||||
String lastClientUuid = userSession.getNote(AuthenticationManager.LAST_AUTHENTICATED_CLIENT);
|
||||
if (lastClientUuid != null) {
|
||||
client = realm.getClientById(lastClientUuid);
|
||||
}
|
||||
|
||||
if (client != null) {
|
||||
session.getContext().setClient(client);
|
||||
} else {
|
||||
loginForm.setAttribute("skipLink", true);
|
||||
if (client == null) {
|
||||
loginForm.setAttribute(Constants.SKIP_LINK, true);
|
||||
}
|
||||
|
||||
response = loginForm.createInfoPage();
|
||||
|
@ -234,7 +239,7 @@ public class SessionCodeChecks {
|
|||
// In case that is replayed action, but sent to the same FORM like actual FORM, we just re-render the page
|
||||
if (ObjectUtil.isEqualOrBothNull(execution, authSession.getAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION))) {
|
||||
String latestFlowPath = authSession.getAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH);
|
||||
URI redirectUri = getLastExecutionUrl(latestFlowPath, execution);
|
||||
URI redirectUri = getLastExecutionUrl(latestFlowPath, execution, client.getClientId());
|
||||
|
||||
logger.debugf("Invalid action code, but execution matches. So just redirecting to %s", redirectUri);
|
||||
authSession.setAuthNote(LoginActionsService.FORWARDED_ERROR_MESSAGE_NOTE, Messages.EXPIRED_ACTION);
|
||||
|
@ -289,7 +294,7 @@ public class SessionCodeChecks {
|
|||
|
||||
authSession.setAuthNote(LoginActionsService.FORWARDED_ERROR_MESSAGE_NOTE, Messages.LOGIN_TIMEOUT);
|
||||
|
||||
URI redirectUri = getLastExecutionUrl(LoginActionsService.AUTHENTICATE_PATH, null);
|
||||
URI redirectUri = getLastExecutionUrl(LoginActionsService.AUTHENTICATE_PATH, null, authSession.getClient().getClientId());
|
||||
logger.debugf("Flow restart after timeout. Redirecting to %s", redirectUri);
|
||||
response = Response.status(Response.Status.FOUND).location(redirectUri).build();
|
||||
return false;
|
||||
|
@ -351,7 +356,7 @@ public class SessionCodeChecks {
|
|||
flowPath = LoginActionsService.AUTHENTICATE_PATH;
|
||||
}
|
||||
|
||||
URI redirectUri = getLastExecutionUrl(flowPath, null);
|
||||
URI redirectUri = getLastExecutionUrl(flowPath, null, authSession.getClient().getClientId());
|
||||
logger.debugf("Authentication session restart from cookie succeeded. Redirecting to %s", redirectUri);
|
||||
return Response.status(Response.Status.FOUND).location(redirectUri).build();
|
||||
} else {
|
||||
|
@ -367,16 +372,20 @@ public class SessionCodeChecks {
|
|||
.path(LoginActionsService.REQUIRED_ACTION);
|
||||
|
||||
if (action != null) {
|
||||
uriBuilder.queryParam("execution", action);
|
||||
uriBuilder.queryParam(Constants.EXECUTION, action);
|
||||
}
|
||||
|
||||
ClientModel client = authSession.getClient();
|
||||
uriBuilder.queryParam(Constants.CLIENT_ID, client.getClientId());
|
||||
|
||||
URI redirect = uriBuilder.build(realm.getName());
|
||||
return Response.status(302).location(redirect).build();
|
||||
}
|
||||
|
||||
|
||||
private URI getLastExecutionUrl(String flowPath, String executionId) {
|
||||
private URI getLastExecutionUrl(String flowPath, String executionId, String clientId) {
|
||||
return new AuthenticationFlowURLHelper(session, realm, uriInfo)
|
||||
.getLastExecutionUrl(flowPath, executionId);
|
||||
.getLastExecutionUrl(flowPath, executionId, clientId);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ import javax.ws.rs.core.UriInfo;
|
|||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.authentication.AuthenticationProcessor;
|
||||
import org.keycloak.forms.login.LoginFormsProvider;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.protocol.AuthorizationEndpointBase;
|
||||
|
@ -61,13 +62,15 @@ public class AuthenticationFlowURLHelper {
|
|||
}
|
||||
|
||||
|
||||
public URI getLastExecutionUrl(String flowPath, String executionId) {
|
||||
public URI getLastExecutionUrl(String flowPath, String executionId, String clientId) {
|
||||
UriBuilder uriBuilder = LoginActionsService.loginActionsBaseUrl(uriInfo)
|
||||
.path(flowPath);
|
||||
|
||||
if (executionId != null) {
|
||||
uriBuilder.queryParam("execution", executionId);
|
||||
uriBuilder.queryParam(Constants.EXECUTION, executionId);
|
||||
}
|
||||
uriBuilder.queryParam(Constants.CLIENT_ID, clientId);
|
||||
|
||||
return uriBuilder.build(realm.getName());
|
||||
}
|
||||
|
||||
|
@ -84,7 +87,7 @@ public class AuthenticationFlowURLHelper {
|
|||
latestFlowPath = LoginActionsService.AUTHENTICATE_PATH;
|
||||
}
|
||||
|
||||
return getLastExecutionUrl(latestFlowPath, executionId);
|
||||
return getLastExecutionUrl(latestFlowPath, executionId, authSession.getClient().getClientId());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -74,7 +74,7 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
|
|||
Twitter twitter = new TwitterFactory().getInstance();
|
||||
twitter.setOAuthConsumer(getConfig().getClientId(), getConfig().getClientSecret());
|
||||
|
||||
URI uri = new URI(request.getRedirectUri() + "?state=" + request.getState());
|
||||
URI uri = new URI(request.getRedirectUri() + "?state=" + request.getState().getEncodedState());
|
||||
|
||||
RequestToken requestToken = twitter.getOAuthRequestToken(uri.toString());
|
||||
AuthenticationSessionModel authSession = request.getAuthenticationSession();
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
|
||||
/**
|
||||
* Helper for parse action-uri from the HTML login page and do something with it (eg. open in new browser, parse code parameter and use it somewhere else etc)
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class ActionURIUtils {
|
||||
|
||||
private static final Pattern ACTION_URI_PATTERN = Pattern.compile("action=\"([^\"]+)\"");
|
||||
|
||||
private static final Pattern QUERY_STRING_PATTERN = Pattern.compile("[^\\?]+\\?([^#]+).*");
|
||||
|
||||
private static final Pattern PARAMS_PATTERN = Pattern.compile("[=\\&]");
|
||||
|
||||
public static String getActionURIFromPageSource(String htmlPageSource) {
|
||||
Matcher m = ACTION_URI_PATTERN.matcher(htmlPageSource);
|
||||
if (m.find()) {
|
||||
return m.group(1).replaceAll("&", "&");
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Map<String, String> parseQueryParamsFromActionURI(String actionURI) {
|
||||
Matcher m = QUERY_STRING_PATTERN.matcher(actionURI);
|
||||
if (m.find()) {
|
||||
String queryString = m.group(1);
|
||||
|
||||
String[] params = PARAMS_PATTERN.split(queryString, 0);
|
||||
Map<String, String> result = new HashMap<>(); // Don't take multivalued into account for now
|
||||
|
||||
for (int i=0 ; i<params.length ; i+=2) {
|
||||
String paramName = params[i];
|
||||
String paramValue = params[i+1];
|
||||
result.put(paramName, paramValue);
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
}
|
||||
|
||||
public static String removeQueryParamFromURI(String actionURI, String paramName) {
|
||||
return UriBuilder.fromUri(actionURI)
|
||||
.replaceQueryParam(paramName, null)
|
||||
.build().toString();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
private static final String TEST = "<form id=\"kc-form-login\" class=\"form-horizontal\" action=\"http://localhost:8180/auth/realms/child/login-actions/authenticate?code=1WnqOmapgo0cj3mpRQ-vbleIKUJdwFzonzy1fjvnWQQ&execution=3ac92a20-9c31-49de-a3c8-f2a4fff80986&client_id=client-linking\" method=\"post\">";
|
||||
|
||||
public static void main(String[] args) {
|
||||
String actionURI = getActionURIFromPageSource(TEST);
|
||||
System.out.println("action uri: " + actionURI);
|
||||
|
||||
Map<String, String> params = parseQueryParamsFromActionURI(actionURI);
|
||||
System.out.println("params: " + params);
|
||||
|
||||
String actionURI2 = removeQueryParamFromURI(actionURI, "execution");
|
||||
System.out.println("action uri 2: " + actionURI2);
|
||||
}*/
|
||||
}
|
|
@ -43,6 +43,14 @@ public class ErrorPage extends AbstractPage {
|
|||
backToApplicationLink.click();
|
||||
}
|
||||
|
||||
public String getBackToApplicationLink() {
|
||||
if (backToApplicationLink == null) {
|
||||
return null;
|
||||
} else {
|
||||
return backToApplicationLink.getAttribute("href");
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isCurrent() {
|
||||
return driver.getTitle() != null && driver.getTitle().equals("We're sorry...");
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.junit.Test;
|
|||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
|
@ -32,6 +33,7 @@ import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
|||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.pages.AppPage;
|
||||
import org.keycloak.testsuite.pages.AppPage.RequestType;
|
||||
import org.keycloak.testsuite.pages.ErrorPage;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
import org.keycloak.testsuite.pages.LoginUpdateProfileEditUsernameAllowedPage;
|
||||
import org.keycloak.testsuite.util.UserBuilder;
|
||||
|
@ -53,6 +55,9 @@ public class RequiredActionUpdateProfileTest extends AbstractTestRealmKeycloakTe
|
|||
@Page
|
||||
protected LoginUpdateProfileEditUsernameAllowedPage updateProfilePage;
|
||||
|
||||
@Page
|
||||
protected ErrorPage errorPage;
|
||||
|
||||
@Override
|
||||
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||
ActionUtil.addRequiredActionForUser(testRealm, "test-user@localhost", UserModel.RequiredAction.UPDATE_PROFILE.name());
|
||||
|
@ -294,4 +299,23 @@ public class RequiredActionUpdateProfileTest extends AbstractTestRealmKeycloakTe
|
|||
events.assertEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateProfileExpiredCookies() {
|
||||
loginPage.open();
|
||||
loginPage.login("john-doh@localhost", "password");
|
||||
|
||||
updateProfilePage.assertCurrent();
|
||||
|
||||
// Expire cookies and assert the page with "back to application" link present
|
||||
driver.manage().deleteAllCookies();
|
||||
|
||||
updateProfilePage.update("New first", "New last", "keycloak-user@localhost", "test-user@localhost");
|
||||
errorPage.assertCurrent();
|
||||
|
||||
String backToAppLink = errorPage.getBackToApplicationLink();
|
||||
|
||||
ClientRepresentation client = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app").toRepresentation();
|
||||
Assert.assertEquals(backToAppLink, client.getBaseUrl());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import org.jboss.shrinkwrap.api.spec.WebArchive;
|
|||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.common.util.Base64Url;
|
||||
|
@ -37,6 +38,7 @@ import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
|||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.RoleRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.ActionURIUtils;
|
||||
import org.keycloak.testsuite.adapter.AbstractServletsAdapterTest;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
|
||||
|
@ -57,6 +59,7 @@ import javax.ws.rs.core.UriBuilder;
|
|||
import java.net.URL;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
@ -496,23 +499,11 @@ public abstract class AbstractClientInitiatedAccountLinkTest extends AbstractSer
|
|||
|
||||
// ok, now scrape the code from page
|
||||
String pageSource = driver.getPageSource();
|
||||
Pattern p = Pattern.compile("action=\"(.+)\"");
|
||||
Matcher m = p.matcher(pageSource);
|
||||
String action = null;
|
||||
if (m.find()) {
|
||||
action = m.group(1);
|
||||
String action = ActionURIUtils.getActionURIFromPageSource(pageSource);
|
||||
System.out.println("action uri: " + action);
|
||||
|
||||
}
|
||||
System.out.println("action: " + action);
|
||||
|
||||
p = Pattern.compile("code=(.+)&");
|
||||
m = p.matcher(action);
|
||||
String code = null;
|
||||
if (m.find()) {
|
||||
code = m.group(1);
|
||||
|
||||
}
|
||||
System.out.println("code: " + code);
|
||||
Map<String, String> queryParams = ActionURIUtils.parseQueryParamsFromActionURI(action);
|
||||
System.out.println("query params: " + queryParams);
|
||||
|
||||
// now try and use the code to login to remote link-only idp
|
||||
|
||||
|
@ -520,7 +511,8 @@ public abstract class AbstractClientInitiatedAccountLinkTest extends AbstractSer
|
|||
|
||||
uri = UriBuilder.fromUri(AuthServerTestEnricher.getAuthServerContextRoot())
|
||||
.path(uri)
|
||||
.queryParam("code", code)
|
||||
.queryParam(OAuth2Constants.CODE, queryParams.get(OAuth2Constants.CODE))
|
||||
.queryParam(Constants.CLIENT_ID, queryParams.get(Constants.CLIENT_ID))
|
||||
.build().toString();
|
||||
|
||||
System.out.println("hack uri: " + uri);
|
||||
|
|
|
@ -387,4 +387,22 @@ public abstract class AbstractBrokerTest extends AbstractBaseBrokerTest {
|
|||
logoutFromRealm(bc.providerRealmName());
|
||||
logoutFromRealm(bc.consumerRealmName());
|
||||
}
|
||||
|
||||
|
||||
// KEYCLOAK-4016
|
||||
@Test
|
||||
public void testExpiredCode() {
|
||||
driver.navigate().to(getAccountUrl(bc.consumerRealmName()));
|
||||
|
||||
log.debug("Expire all browser cookies");
|
||||
driver.manage().deleteAllCookies();
|
||||
|
||||
log.debug("Clicking social " + bc.getIDPAlias());
|
||||
accountLoginPage.clickSocial(bc.getIDPAlias());
|
||||
|
||||
waitForPage(driver, "sorry");
|
||||
errorPage.assertCurrent();
|
||||
String link = errorPage.getBackToApplicationLink();
|
||||
Assert.assertTrue(link.endsWith("/auth/realms/consumer/account"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ import org.keycloak.representations.idm.ComponentRepresentation;
|
|||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.storage.UserStorageProvider;
|
||||
import org.keycloak.storage.UserStorageProviderModel;
|
||||
import org.keycloak.testsuite.ActionURIUtils;
|
||||
import org.keycloak.testsuite.util.KerberosRule;
|
||||
|
||||
/**
|
||||
|
@ -156,10 +157,7 @@ public class KerberosStandaloneTest extends AbstractKerberosTest {
|
|||
|
||||
Assert.assertTrue(context.contains("Log in to test"));
|
||||
|
||||
Pattern pattern = Pattern.compile("action=\"([^\"]+)\"");
|
||||
Matcher m = pattern.matcher(context);
|
||||
Assert.assertTrue(m.find());
|
||||
String url = m.group(1);
|
||||
String url = ActionURIUtils.getActionURIFromPageSource(context);
|
||||
|
||||
|
||||
// Follow login with HttpClient. Improve if needed
|
||||
|
|
|
@ -27,6 +27,7 @@ import org.keycloak.events.Errors;
|
|||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.models.BrowserSecurityHeaders;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.EventRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
|
@ -54,6 +55,7 @@ import static org.junit.Assert.assertFalse;
|
|||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.keycloak.testsuite.admin.ApiUtil.findClientByClientId;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
|
@ -600,9 +602,13 @@ public class LoginTest extends AbstractTestRealmKeycloakTest {
|
|||
|
||||
driver.manage().deleteAllCookies();
|
||||
|
||||
// Cookies are expired including KC_RESTART. No way to continue login. Error page must be shown
|
||||
// Cookies are expired including KC_RESTART. No way to continue login. Error page must be shown with the "back to application" link
|
||||
loginPage.login("login@test.com", "password");
|
||||
errorPage.assertCurrent();
|
||||
String link = errorPage.getBackToApplicationLink();
|
||||
|
||||
ClientRepresentation thirdParty = findClientByClientId(adminClient.realm("test"), "third-party").toRepresentation();
|
||||
Assert.assertNotNull(link, thirdParty.getBaseUrl());
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ import org.keycloak.models.UserModel;
|
|||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||
import org.keycloak.testsuite.ActionURIUtils;
|
||||
import org.keycloak.testsuite.Assert;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
|
@ -120,22 +121,16 @@ public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
|
|||
public void openMultipleTabs() {
|
||||
oauth.openLoginForm();
|
||||
loginPage.assertCurrent();
|
||||
String actionUrl1 = getActionUrl(driver.getPageSource());
|
||||
String actionUrl1 = ActionURIUtils.getActionURIFromPageSource(driver.getPageSource());
|
||||
|
||||
oauth.openLoginForm();
|
||||
loginPage.assertCurrent();
|
||||
String actionUrl2 = getActionUrl(driver.getPageSource());
|
||||
String actionUrl2 = ActionURIUtils.getActionURIFromPageSource(driver.getPageSource());
|
||||
|
||||
Assert.assertEquals(actionUrl1, actionUrl2);
|
||||
|
||||
}
|
||||
|
||||
|
||||
private String getActionUrl(String pageSource) {
|
||||
return pageSource.split("action=\"")[1].split("\"")[0].replaceAll("&", "&");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void multipleTabsParallelLoginTest() {
|
||||
oauth.openLoginForm();
|
||||
|
@ -173,7 +168,7 @@ public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
|
|||
// Simulate to open login form in 2 tabs
|
||||
oauth.openLoginForm();
|
||||
loginPage.assertCurrent();
|
||||
String actionUrl1 = getActionUrl(driver.getPageSource());
|
||||
String actionUrl1 = ActionURIUtils.getActionURIFromPageSource(driver.getPageSource());
|
||||
|
||||
// Click "register" in tab2
|
||||
loginPage.clickRegister();
|
||||
|
@ -204,7 +199,7 @@ public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
|
|||
// Simulate to open login form in 2 tabs
|
||||
oauth.openLoginForm();
|
||||
loginPage.assertCurrent();
|
||||
String actionUrl1 = getActionUrl(driver.getPageSource());
|
||||
String actionUrl1 = ActionURIUtils.getActionURIFromPageSource(driver.getPageSource());
|
||||
|
||||
loginPage.login("invalid", "invalid");
|
||||
loginPage.assertCurrent();
|
||||
|
@ -228,7 +223,7 @@ public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
|
|||
// Open tab1
|
||||
oauth.openLoginForm();
|
||||
loginPage.assertCurrent();
|
||||
String actionUrl1 = getActionUrl(driver.getPageSource());
|
||||
String actionUrl1 = ActionURIUtils.getActionURIFromPageSource(driver.getPageSource());
|
||||
|
||||
// Authenticate in tab2
|
||||
loginPage.login("login-test", "password");
|
||||
|
@ -253,8 +248,8 @@ public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
|
|||
oauth.openLoginForm();
|
||||
|
||||
// Manually remove execution from the URL and try to simulate the request just with "code" parameter
|
||||
String actionUrl = driver.getPageSource().split("action=\"")[1].split("\"")[0].replaceAll("&", "&");
|
||||
actionUrl = actionUrl.replaceFirst("&execution=.*", "");
|
||||
String actionUrl = ActionURIUtils.getActionURIFromPageSource(driver.getPageSource());
|
||||
actionUrl = ActionURIUtils.removeQueryParamFromURI(actionUrl, Constants.EXECUTION);
|
||||
|
||||
driver.navigate().to(actionUrl);
|
||||
|
||||
|
@ -272,8 +267,8 @@ public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
|
|||
updatePasswordPage.assertCurrent();
|
||||
|
||||
// Manually remove execution from the URL and try to simulate the request just with "code" parameter
|
||||
String actionUrl = driver.getPageSource().split("action=\"")[1].split("\"")[0].replaceAll("&", "&");
|
||||
actionUrl = actionUrl.replaceFirst("&execution=.*", "");
|
||||
String actionUrl = ActionURIUtils.getActionURIFromPageSource(driver.getPageSource());
|
||||
actionUrl = ActionURIUtils.removeQueryParamFromURI(actionUrl, Constants.EXECUTION);
|
||||
|
||||
driver.navigate().to(actionUrl);
|
||||
|
||||
|
|
|
@ -418,6 +418,51 @@ public class ResetPasswordTest extends AbstractTestRealmKeycloakTest {
|
|||
}
|
||||
}
|
||||
|
||||
// KEYCLOAK-4016
|
||||
@Test
|
||||
public void resetPasswordExpiredCodeAndAuthSession() throws IOException, MessagingException, InterruptedException {
|
||||
final AtomicInteger originalValue = new AtomicInteger();
|
||||
|
||||
RealmRepresentation realmRep = testRealm().toRepresentation();
|
||||
originalValue.set(realmRep.getActionTokenGeneratedByUserLifespan());
|
||||
realmRep.setActionTokenGeneratedByUserLifespan(60);
|
||||
testRealm().update(realmRep);
|
||||
|
||||
try {
|
||||
initiateResetPasswordFromResetPasswordPage("login-test");
|
||||
|
||||
events.expectRequiredAction(EventType.SEND_RESET_PASSWORD)
|
||||
.session((String)null)
|
||||
.user(userId).detail(Details.USERNAME, "login-test").detail(Details.EMAIL, "login@test.com").assertEvent();
|
||||
|
||||
assertEquals(1, greenMail.getReceivedMessages().length);
|
||||
|
||||
MimeMessage message = greenMail.getReceivedMessages()[0];
|
||||
|
||||
String changePasswordUrl = getPasswordResetEmailLink(message);
|
||||
|
||||
setTimeOffset(70);
|
||||
|
||||
log.debug("Going to reset password URI.");
|
||||
driver.navigate().to(oauth.AUTH_SERVER_ROOT + "/realms/test/login-actions/reset-credentials"); // This is necessary to delete KC_RESTART cookie that is restricted to /auth/realms/test path
|
||||
log.debug("Removing cookies.");
|
||||
driver.manage().deleteAllCookies();
|
||||
driver.navigate().to(changePasswordUrl.trim());
|
||||
|
||||
errorPage.assertCurrent();
|
||||
Assert.assertEquals("Reset Credential not allowed", errorPage.getError());
|
||||
String backToAppLink = errorPage.getBackToApplicationLink();
|
||||
Assert.assertTrue(backToAppLink.endsWith("/app/auth"));
|
||||
|
||||
events.expectRequiredAction(EventType.EXECUTE_ACTION_TOKEN_ERROR).error("expired_code").client((String) null).user(userId).session((String) null).clearDetails().detail(Details.ACTION, ResetCredentialsActionToken.TOKEN_TYPE).assertEvent();
|
||||
} finally {
|
||||
setTimeOffset(0);
|
||||
|
||||
realmRep.setActionTokenGeneratedByUserLifespan(originalValue.get());
|
||||
testRealm().update(realmRep);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resetPasswordDisabledUser() throws IOException, MessagingException, InterruptedException {
|
||||
UserRepresentation user = findUser("login-test");
|
||||
|
|
|
@ -56,6 +56,7 @@ import org.keycloak.representations.idm.RealmRepresentation;
|
|||
import org.keycloak.representations.idm.RoleRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||
import org.keycloak.testsuite.ActionURIUtils;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
|
||||
import org.keycloak.testsuite.util.ClientBuilder;
|
||||
|
@ -210,7 +211,8 @@ public class AccessTokenTest extends AbstractKeycloakTest {
|
|||
oauth.redirectUri(AuthServerTestEnricher.getAuthServerContextRoot() + "/auth/admin/test/console/nosuch.html");
|
||||
oauth.openLoginForm();
|
||||
|
||||
String loginPageCode = driver.getPageSource().split("code=")[1].split("&")[0].split("\"")[0];
|
||||
String actionURI = ActionURIUtils.getActionURIFromPageSource(driver.getPageSource());
|
||||
String loginPageCode = ActionURIUtils.parseQueryParamsFromActionURI(actionURI).get("code");
|
||||
|
||||
oauth.fillLoginForm("test-user@localhost", "password");
|
||||
|
||||
|
@ -441,7 +443,8 @@ public class AccessTokenTest extends AbstractKeycloakTest {
|
|||
|
||||
oauth.doLogin("test-user@localhost", "password");
|
||||
|
||||
String code = driver.getPageSource().split("code=")[1].split("&")[0].split("\"")[0];
|
||||
String actionURI = ActionURIUtils.getActionURIFromPageSource(driver.getPageSource());
|
||||
String code = ActionURIUtils.parseQueryParamsFromActionURI(actionURI).get("code");
|
||||
|
||||
OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password");
|
||||
Assert.assertEquals(400, response.getStatusCode());
|
||||
|
|
|
@ -36,6 +36,7 @@ import org.keycloak.models.Constants;
|
|||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||
import org.keycloak.testsuite.ActionURIUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URLEncoder;
|
||||
|
@ -71,10 +72,7 @@ public class LoginStatusIframeEndpointTest extends AbstractKeycloakTest {
|
|||
String s = IOUtils.toString(response.getEntity().getContent());
|
||||
response.close();
|
||||
|
||||
Matcher matcher = Pattern.compile("action=\"([^\"]*)\"").matcher(s);
|
||||
matcher.find();
|
||||
|
||||
String action = matcher.group(1);
|
||||
String action = ActionURIUtils.getActionURIFromPageSource(s);
|
||||
|
||||
HttpPost post = new HttpPost(action);
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ import org.keycloak.testsuite.account.AccountTest;
|
|||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.pages.AccountApplicationsPage;
|
||||
import org.keycloak.testsuite.pages.AppPage;
|
||||
import org.keycloak.testsuite.pages.ErrorPage;
|
||||
import org.keycloak.testsuite.pages.OAuthGrantPage;
|
||||
import org.keycloak.testsuite.util.ClientManager;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
|
@ -77,6 +78,9 @@ public class OAuthGrantTest extends AbstractKeycloakTest {
|
|||
@Page
|
||||
protected AppPage appPage;
|
||||
|
||||
@Page
|
||||
protected ErrorPage errorPage;
|
||||
|
||||
@Override
|
||||
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
||||
|
||||
|
@ -405,4 +409,23 @@ public class OAuthGrantTest extends AbstractKeycloakTest {
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void oauthGrantExpiredAuthSession() throws Exception {
|
||||
oauth.clientId(THIRD_PARTY_APP);
|
||||
oauth.doLoginGrant("test-user@localhost", "password");
|
||||
|
||||
grantPage.assertCurrent();
|
||||
|
||||
// Expire cookies
|
||||
driver.manage().deleteAllCookies();
|
||||
|
||||
grantPage.accept();
|
||||
|
||||
// Assert link "back to application" present
|
||||
errorPage.assertCurrent();
|
||||
String backToAppLink = errorPage.getBackToApplicationLink();
|
||||
ClientRepresentation thirdParty = findClientByClientId(adminClient.realm(REALM_NAME), THIRD_PARTY_APP).toRepresentation();
|
||||
Assert.assertEquals(backToAppLink, thirdParty.getBaseUrl());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -162,6 +162,7 @@
|
|||
"enabled": true,
|
||||
"consentRequired": true,
|
||||
|
||||
"baseUrl": "http://localhost:8180/auth/realms/master/app/auth",
|
||||
"redirectUris": [
|
||||
"http://localhost:8180/auth/realms/master/app/*"
|
||||
],
|
||||
|
|
|
@ -82,8 +82,13 @@ log4j.logger.org.apache.directory.server.ldap.LdapProtocolHandler=error
|
|||
#log4j.logger.org.apache.http.impl.conn=debug
|
||||
|
||||
# Enable to view details from identity provider authenticator
|
||||
log4j.logger.org.keycloak.authentication.authenticators.browser.IdentityProviderAuthenticator=trace
|
||||
log4j.logger.org.keycloak.services.resources.IdentityBrokerService=trace
|
||||
log4j.logger.org.keycloak.broker=trace
|
||||
#log4j.logger.org.keycloak.authentication.authenticators.browser.IdentityProviderAuthenticator=trace
|
||||
#log4j.logger.org.keycloak.services.resources.IdentityBrokerService=trace
|
||||
#log4j.logger.org.keycloak.broker=trace
|
||||
|
||||
# log4j.logger.io.undertow=trace
|
||||
#log4j.logger.io.undertow=trace
|
||||
|
||||
#log4j.logger.org.keycloak.protocol=debug
|
||||
#log4j.logger.org.keycloak.services.resources.LoginActionsService=debug
|
||||
#log4j.logger.org.keycloak.services.managers=debug
|
||||
#log4j.logger.org.keycloak.services.resources.SessionCodeChecks=debug
|
Loading…
Reference in a new issue