KEYCLOAK-14483 Broker state param fix
This commit is contained in:
parent
52a939f61a
commit
41dc94fead
8 changed files with 324 additions and 211 deletions
|
@ -42,7 +42,6 @@ public class BrokeredIdentityContext {
|
|||
private String lastName;
|
||||
private String brokerSessionId;
|
||||
private String brokerUserId;
|
||||
private String code;
|
||||
private String token;
|
||||
private IdentityProviderModel idpConfig;
|
||||
private IdentityProvider idp;
|
||||
|
@ -136,14 +135,6 @@ public class BrokeredIdentityContext {
|
|||
this.token = token;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public IdentityProviderModel getIdpConfig() {
|
||||
return idpConfig;
|
||||
}
|
||||
|
|
|
@ -38,18 +38,39 @@ public interface IdentityProvider<C extends IdentityProviderModel> extends Provi
|
|||
String FEDERATED_ACCESS_TOKEN = "FEDERATED_ACCESS_TOKEN";
|
||||
|
||||
interface AuthenticationCallback {
|
||||
|
||||
/**
|
||||
* Common method to return current authenticationSession and verify if it is not expired
|
||||
*
|
||||
* @param encodedCode
|
||||
* @return see description
|
||||
*/
|
||||
AuthenticationSessionModel getAndVerifyAuthenticationSession(String encodedCode);
|
||||
|
||||
/**
|
||||
* This method should be called by provider after the JAXRS callback endpoint has finished authentication
|
||||
* with the remote IDP
|
||||
* with the remote IDP. There is an assumption that authenticationSession is set in the context when this method is called
|
||||
*
|
||||
* @param context
|
||||
* @return
|
||||
* @return see description
|
||||
*/
|
||||
Response authenticated(BrokeredIdentityContext context);
|
||||
|
||||
Response cancelled(String code);
|
||||
/**
|
||||
* Called when user cancelled authentication on the IDP side - for example user didn't approve consent page on the IDP side.
|
||||
* Assumption is that authenticationSession is set in the {@link org.keycloak.models.KeycloakContext} when this method is called
|
||||
*
|
||||
* @return see description
|
||||
*/
|
||||
Response cancelled();
|
||||
|
||||
Response error(String code, String message);
|
||||
/**
|
||||
* Called when error happened on the IDP side.
|
||||
* Assumption is that authenticationSession is set in the {@link org.keycloak.models.KeycloakContext} when this method is called
|
||||
*
|
||||
* @return see description
|
||||
*/
|
||||
Response error(String message);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -479,18 +479,20 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
|
|||
return errorIdentityProviderLogin(Messages.IDENTITY_PROVIDER_MISSING_STATE_ERROR);
|
||||
}
|
||||
|
||||
if (error != null) {
|
||||
logger.error(error + " for broker login " + getConfig().getProviderId());
|
||||
if (error.equals(ACCESS_DENIED)) {
|
||||
return callback.cancelled(state);
|
||||
} else if (error.equals(OAuthErrorException.LOGIN_REQUIRED) || error.equals(OAuthErrorException.INTERACTION_REQUIRED)) {
|
||||
return callback.error(state, error);
|
||||
} else {
|
||||
return callback.error(state, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
AuthenticationSessionModel authSession = this.callback.getAndVerifyAuthenticationSession(state);
|
||||
session.getContext().setAuthenticationSession(authSession);
|
||||
|
||||
if (error != null) {
|
||||
logger.error(error + " for broker login " + getConfig().getProviderId());
|
||||
if (error.equals(ACCESS_DENIED)) {
|
||||
return callback.cancelled();
|
||||
} else if (error.equals(OAuthErrorException.LOGIN_REQUIRED) || error.equals(OAuthErrorException.INTERACTION_REQUIRED)) {
|
||||
return callback.error(error);
|
||||
} else {
|
||||
return callback.error(Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
if (authorizationCode != null) {
|
||||
String response = generateTokenRequest(authorizationCode).asString();
|
||||
|
@ -505,7 +507,7 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
|
|||
|
||||
federatedIdentity.setIdpConfig(getConfig());
|
||||
federatedIdentity.setIdp(AbstractOAuth2IdentityProvider.this);
|
||||
federatedIdentity.setCode(state);
|
||||
federatedIdentity.setAuthenticationSession(authSession);
|
||||
|
||||
return callback.authenticated(federatedIdentity);
|
||||
}
|
||||
|
@ -518,7 +520,7 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
|
|||
}
|
||||
|
||||
private Response errorIdentityProviderLogin(String message) {
|
||||
event.event(EventType.LOGIN);
|
||||
event.event(EventType.IDENTITY_PROVIDER_LOGIN);
|
||||
event.error(Errors.IDENTITY_PROVIDER_LOGIN_FAILURE);
|
||||
return ErrorPage.error(session, null, Response.Status.BAD_GATEWAY, message);
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ package org.keycloak.broker.saml;
|
|||
import org.jboss.logging.Logger;
|
||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||
|
||||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||
import org.keycloak.broker.provider.IdentityProvider;
|
||||
|
@ -39,13 +40,17 @@ import org.keycloak.events.Details;
|
|||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeyManager;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.protocol.LoginProtocol;
|
||||
import org.keycloak.protocol.LoginProtocolFactory;
|
||||
import org.keycloak.protocol.saml.JaxrsSAML2BindingBuilder;
|
||||
import org.keycloak.protocol.saml.SamlProtocol;
|
||||
import org.keycloak.protocol.saml.SamlProtocolUtils;
|
||||
import org.keycloak.protocol.saml.SamlService;
|
||||
import org.keycloak.protocol.saml.SamlSessionUtils;
|
||||
import org.keycloak.protocol.saml.preprocessor.SamlAuthenticationPreprocessor;
|
||||
import org.keycloak.saml.SAML2LogoutResponseBuilder;
|
||||
|
@ -63,6 +68,7 @@ import org.keycloak.saml.processing.core.util.XMLSignatureUtil;
|
|||
import org.keycloak.saml.processing.web.util.PostBindingUtil;
|
||||
import org.keycloak.services.ErrorPage;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.managers.ClientSessionCode;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
|
@ -88,6 +94,7 @@ import java.util.Iterator;
|
|||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
@ -99,6 +106,8 @@ import org.keycloak.rotation.KeyLocator;
|
|||
import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
|
||||
import org.keycloak.saml.validators.ConditionsValidator;
|
||||
import org.keycloak.saml.validators.DestinationValidator;
|
||||
import org.keycloak.services.util.CacheControlUtil;
|
||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
|
@ -122,7 +131,6 @@ public class SAMLEndpoint {
|
|||
public static final String SAML_FEDERATED_SUBJECT_NAMEID = "SAML_FEDERATED_SUBJECT_NAME_ID";
|
||||
public static final String SAML_LOGIN_RESPONSE = "SAML_LOGIN_RESPONSE";
|
||||
public static final String SAML_ASSERTION = "SAML_ASSERTION";
|
||||
public static final String SAML_IDP_INITIATED_CLIENT_ID = "SAML_IDP_INITIATED_CLIENT_ID";
|
||||
public static final String SAML_AUTHN_STATEMENT = "SAML_AUTHN_STATEMENT";
|
||||
protected RealmModel realm;
|
||||
protected EventBuilder event;
|
||||
|
@ -391,13 +399,21 @@ public class SAMLEndpoint {
|
|||
protected Response handleLoginResponse(String samlResponse, SAMLDocumentHolder holder, ResponseType responseType, String relayState, String clientId) {
|
||||
|
||||
try {
|
||||
AuthenticationSessionModel authSession;
|
||||
if (clientId != null && ! clientId.trim().isEmpty()) {
|
||||
authSession = samlIdpInitiatedSSO(clientId);
|
||||
} else {
|
||||
authSession = callback.getAndVerifyAuthenticationSession(relayState);
|
||||
}
|
||||
session.getContext().setAuthenticationSession(authSession);
|
||||
|
||||
KeyManager.ActiveRsaKey keys = session.keys().getActiveRsaKey(realm);
|
||||
if (! isSuccessfulSamlResponse(responseType)) {
|
||||
String statusMessage = responseType.getStatus() == null ? Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR : responseType.getStatus().getStatusMessage();
|
||||
return callback.error(relayState, statusMessage);
|
||||
return callback.error(statusMessage);
|
||||
}
|
||||
if (responseType.getAssertions() == null || responseType.getAssertions().isEmpty()) {
|
||||
return callback.error(relayState, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
|
||||
return callback.error(Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
|
||||
}
|
||||
|
||||
boolean assertionIsEncrypted = AssertionUtil.isAssertionEncrypted(responseType);
|
||||
|
@ -406,7 +422,7 @@ public class SAMLEndpoint {
|
|||
logger.error("The assertion is not encrypted, which is required.");
|
||||
event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
|
||||
event.error(Errors.INVALID_SAML_RESPONSE);
|
||||
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.INVALID_REQUESTER);
|
||||
return ErrorPage.error(session, authSession, Response.Status.BAD_REQUEST, Messages.INVALID_REQUESTER);
|
||||
}
|
||||
|
||||
Element assertionElement;
|
||||
|
@ -429,7 +445,7 @@ public class SAMLEndpoint {
|
|||
logger.error("validation failed");
|
||||
event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
|
||||
event.error(Errors.INVALID_SIGNATURE);
|
||||
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.INVALID_REQUESTER);
|
||||
return ErrorPage.error(session, authSession, Response.Status.BAD_REQUEST, Messages.INVALID_REQUESTER);
|
||||
}
|
||||
|
||||
AssertionType assertion = responseType.getAssertions().get(0).getAssertion();
|
||||
|
@ -440,16 +456,14 @@ public class SAMLEndpoint {
|
|||
logger.errorf("no principal in assertion; expected: %s", expectedPrincipalType());
|
||||
event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
|
||||
event.error(Errors.INVALID_SAML_RESPONSE);
|
||||
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.INVALID_REQUESTER);
|
||||
return ErrorPage.error(session, authSession, Response.Status.BAD_REQUEST, Messages.INVALID_REQUESTER);
|
||||
}
|
||||
|
||||
//Map<String, String> notes = new HashMap<>();
|
||||
BrokeredIdentityContext identity = new BrokeredIdentityContext(principal);
|
||||
identity.getContextData().put(SAML_LOGIN_RESPONSE, responseType);
|
||||
identity.getContextData().put(SAML_ASSERTION, assertion);
|
||||
if (clientId != null && ! clientId.trim().isEmpty()) {
|
||||
identity.getContextData().put(SAML_IDP_INITIATED_CLIENT_ID, clientId);
|
||||
}
|
||||
identity.setAuthenticationSession(authSession);
|
||||
|
||||
identity.setUsername(principal);
|
||||
|
||||
|
@ -478,7 +492,7 @@ public class SAMLEndpoint {
|
|||
logger.error("Assertion expired.");
|
||||
event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
|
||||
event.error(Errors.INVALID_SAML_RESPONSE);
|
||||
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.EXPIRED_CODE);
|
||||
return ErrorPage.error(session, authSession, Response.Status.BAD_REQUEST, Messages.EXPIRED_CODE);
|
||||
}
|
||||
|
||||
AuthnStatementType authn = null;
|
||||
|
@ -502,7 +516,6 @@ public class SAMLEndpoint {
|
|||
if (authn != null && authn.getSessionIndex() != null) {
|
||||
identity.setBrokerSessionId(identity.getBrokerUserId() + "." + authn.getSessionIndex());
|
||||
}
|
||||
identity.setCode(relayState);
|
||||
|
||||
|
||||
return callback.authenticated(identity);
|
||||
|
@ -514,6 +527,42 @@ public class SAMLEndpoint {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* If there is a client whose SAML IDP-initiated SSO URL name is set to the
|
||||
* given {@code clientUrlName}, creates a fresh authentication session for that
|
||||
* client and returns a {@link AuthenticationSessionModel} object with that session.
|
||||
* Otherwise returns "client not found" response.
|
||||
*
|
||||
* @param clientUrlName
|
||||
* @return see description
|
||||
*/
|
||||
private AuthenticationSessionModel samlIdpInitiatedSSO(final String clientUrlName) {
|
||||
event.event(EventType.LOGIN);
|
||||
CacheControlUtil.noBackButtonCacheControlHeader();
|
||||
Optional<ClientModel> oClient = SAMLEndpoint.this.realm.getClientsStream()
|
||||
.filter(c -> Objects.equals(c.getAttribute(SamlProtocol.SAML_IDP_INITIATED_SSO_URL_NAME), clientUrlName))
|
||||
.findFirst();
|
||||
|
||||
if (! oClient.isPresent()) {
|
||||
event.error(Errors.CLIENT_NOT_FOUND);
|
||||
Response response = ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.CLIENT_NOT_FOUND);
|
||||
throw new WebApplicationException(response);
|
||||
}
|
||||
|
||||
LoginProtocolFactory factory = (LoginProtocolFactory) session.getKeycloakSessionFactory().getProviderFactory(LoginProtocol.class, SamlProtocol.LOGIN_PROTOCOL);
|
||||
SamlService samlService = (SamlService) factory.createProtocolEndpoint(SAMLEndpoint.this.realm, event);
|
||||
ResteasyProviderFactory.getInstance().injectProperties(samlService);
|
||||
AuthenticationSessionModel authSession = samlService.getOrCreateLoginSessionForIdpInitiatedSso(session, SAMLEndpoint.this.realm, oClient.get(), null);
|
||||
if (authSession == null) {
|
||||
event.error(Errors.INVALID_REDIRECT_URI);
|
||||
Response response = ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.INVALID_REDIRECT_URI);
|
||||
throw new WebApplicationException(response);
|
||||
}
|
||||
|
||||
return authSession;
|
||||
}
|
||||
|
||||
|
||||
private boolean isSuccessfulSamlResponse(ResponseType responseType) {
|
||||
return responseType != null
|
||||
&& responseType.getStatus() != null
|
||||
|
|
|
@ -372,12 +372,10 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
|||
}
|
||||
|
||||
try {
|
||||
ParsedCodeContext parsedCode = parseSessionCode(code, clientId, tabId);
|
||||
if (parsedCode.response != null) {
|
||||
return parsedCode.response;
|
||||
}
|
||||
AuthenticationSessionModel authSession = parseSessionCode(code, clientId, tabId);
|
||||
|
||||
ClientSessionCode<AuthenticationSessionModel> clientSessionCode = parsedCode.clientSessionCode;
|
||||
ClientSessionCode<AuthenticationSessionModel> clientSessionCode = new ClientSessionCode<>(session, realmModel, authSession);
|
||||
clientSessionCode.setAction(AuthenticationSessionModel.Action.AUTHENTICATE.name());
|
||||
IdentityProviderModel identityProviderModel = realmModel.getIdentityProviderByAlias(providerId);
|
||||
if (identityProviderModel == null) {
|
||||
throw new IdentityBrokerException("Identity Provider [" + providerId + "] not found.");
|
||||
|
@ -506,17 +504,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
|||
|
||||
public Response authenticated(BrokeredIdentityContext context) {
|
||||
IdentityProviderModel identityProviderConfig = context.getIdpConfig();
|
||||
|
||||
final ParsedCodeContext parsedCode;
|
||||
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 = parseEncodedSessionCode(context.getCode());
|
||||
}
|
||||
if (parsedCode.response != null) {
|
||||
return parsedCode.response;
|
||||
}
|
||||
ClientSessionCode<AuthenticationSessionModel> clientCode = parsedCode.clientSessionCode;
|
||||
AuthenticationSessionModel authenticationSession = context.getAuthenticationSession();
|
||||
|
||||
String providerId = identityProviderConfig.getAlias();
|
||||
if (!identityProviderConfig.isStoreToken()) {
|
||||
|
@ -525,9 +513,6 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
|||
}
|
||||
context.setToken(null);
|
||||
}
|
||||
|
||||
AuthenticationSessionModel authenticationSession = clientCode.getClientSession();
|
||||
context.setAuthenticationSession(authenticationSession);
|
||||
|
||||
StatusResponseType loginResponse = (StatusResponseType) context.getContextData().get(SAMLEndpoint.SAML_LOGIN_RESPONSE);
|
||||
if (loginResponse != null) {
|
||||
|
@ -629,7 +614,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
|||
}
|
||||
authenticationSession.setAuthenticatedUser(federatedUser);
|
||||
|
||||
return finishOrRedirectToPostBrokerLogin(authenticationSession, context, false, parsedCode.clientSessionCode);
|
||||
return finishOrRedirectToPostBrokerLogin(authenticationSession, context, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -655,15 +640,11 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
|||
public Response afterFirstBrokerLogin(@QueryParam(LoginActionsService.SESSION_CODE) String code,
|
||||
@QueryParam("client_id") String clientId,
|
||||
@QueryParam(Constants.TAB_ID) String tabId) {
|
||||
ParsedCodeContext parsedCode = parseSessionCode(code, clientId, tabId);
|
||||
if (parsedCode.response != null) {
|
||||
return parsedCode.response;
|
||||
}
|
||||
return afterFirstBrokerLogin(parsedCode.clientSessionCode);
|
||||
AuthenticationSessionModel authSession = parseSessionCode(code, clientId, tabId);
|
||||
return afterFirstBrokerLogin(authSession);
|
||||
}
|
||||
|
||||
private Response afterFirstBrokerLogin(ClientSessionCode<AuthenticationSessionModel> clientSessionCode) {
|
||||
AuthenticationSessionModel authSession = clientSessionCode.getClientSession();
|
||||
private Response afterFirstBrokerLogin(AuthenticationSessionModel authSession) {
|
||||
try {
|
||||
this.event.detail(Details.CODE_ID, authSession.getParentSession().getId())
|
||||
.removeDetail("auth_method");
|
||||
|
@ -742,7 +723,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
|||
updateFederatedIdentity(context, federatedUser);
|
||||
}
|
||||
|
||||
return finishOrRedirectToPostBrokerLogin(authSession, context, true, clientSessionCode);
|
||||
return finishOrRedirectToPostBrokerLogin(authSession, context, true);
|
||||
|
||||
} catch (Exception e) {
|
||||
return redirectToErrorPage(authSession, Response.Status.INTERNAL_SERVER_ERROR, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR, e);
|
||||
|
@ -750,12 +731,12 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
|||
}
|
||||
|
||||
|
||||
private Response finishOrRedirectToPostBrokerLogin(AuthenticationSessionModel authSession, BrokeredIdentityContext context, boolean wasFirstBrokerLogin, ClientSessionCode<AuthenticationSessionModel> clientSessionCode) {
|
||||
private Response finishOrRedirectToPostBrokerLogin(AuthenticationSessionModel authSession, BrokeredIdentityContext context, boolean wasFirstBrokerLogin) {
|
||||
String postBrokerLoginFlowId = context.getIdpConfig().getPostBrokerLoginFlowId();
|
||||
if (postBrokerLoginFlowId == null) {
|
||||
|
||||
logger.debugf("Skip redirect to postBrokerLogin flow. PostBrokerLogin flow not set for identityProvider '%s'.", context.getIdpConfig().getAlias());
|
||||
return afterPostBrokerLoginFlowSuccess(authSession, context, wasFirstBrokerLogin, clientSessionCode);
|
||||
return afterPostBrokerLoginFlowSuccess(authSession, context, wasFirstBrokerLogin);
|
||||
} else {
|
||||
|
||||
logger.debugf("Redirect to postBrokerLogin flow after authentication with identityProvider '%s'.", context.getIdpConfig().getAlias());
|
||||
|
@ -783,11 +764,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
|||
public Response afterPostBrokerLoginFlow(@QueryParam(LoginActionsService.SESSION_CODE) String code,
|
||||
@QueryParam("client_id") String clientId,
|
||||
@QueryParam(Constants.TAB_ID) String tabId) {
|
||||
ParsedCodeContext parsedCode = parseSessionCode(code, clientId, tabId);
|
||||
if (parsedCode.response != null) {
|
||||
return parsedCode.response;
|
||||
}
|
||||
AuthenticationSessionModel authenticationSession = parsedCode.clientSessionCode.getClientSession();
|
||||
AuthenticationSessionModel authenticationSession = parseSessionCode(code, clientId, tabId);
|
||||
|
||||
try {
|
||||
SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromAuthenticationSession(authenticationSession, PostBrokerLoginConstants.PBL_BROKERED_IDENTITY_CONTEXT);
|
||||
|
@ -810,13 +787,13 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
|||
authenticationSession.removeAuthNote(PostBrokerLoginConstants.PBL_BROKERED_IDENTITY_CONTEXT);
|
||||
authenticationSession.removeAuthNote(PostBrokerLoginConstants.PBL_AFTER_FIRST_BROKER_LOGIN);
|
||||
|
||||
return afterPostBrokerLoginFlowSuccess(authenticationSession, context, wasFirstBrokerLogin, parsedCode.clientSessionCode);
|
||||
return afterPostBrokerLoginFlowSuccess(authenticationSession, context, wasFirstBrokerLogin);
|
||||
} catch (IdentityBrokerException e) {
|
||||
return redirectToErrorPage(authenticationSession, Response.Status.INTERNAL_SERVER_ERROR, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR, e);
|
||||
}
|
||||
}
|
||||
|
||||
private Response afterPostBrokerLoginFlowSuccess(AuthenticationSessionModel authSession, BrokeredIdentityContext context, boolean wasFirstBrokerLogin, ClientSessionCode<AuthenticationSessionModel> clientSessionCode) {
|
||||
private Response afterPostBrokerLoginFlowSuccess(AuthenticationSessionModel authSession, BrokeredIdentityContext context, boolean wasFirstBrokerLogin) {
|
||||
String providerId = context.getIdpConfig().getAlias();
|
||||
UserModel federatedUser = authSession.getAuthenticatedUser();
|
||||
|
||||
|
@ -831,7 +808,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
|||
SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromAuthenticationSession(authSession, AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE);
|
||||
authSession.setAuthNote(AbstractIdpAuthenticator.FIRST_BROKER_LOGIN_SUCCESS, serializedCtx.getIdentityProviderId());
|
||||
|
||||
return afterFirstBrokerLogin(clientSessionCode);
|
||||
return afterFirstBrokerLogin(authSession);
|
||||
} else {
|
||||
return finishBrokerAuthentication(context, federatedUser, authSession, providerId);
|
||||
}
|
||||
|
@ -873,40 +850,32 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
|||
|
||||
|
||||
@Override
|
||||
public Response cancelled(String code) {
|
||||
ParsedCodeContext parsedCode = parseEncodedSessionCode(code);
|
||||
if (parsedCode.response != null) {
|
||||
return parsedCode.response;
|
||||
}
|
||||
ClientSessionCode<AuthenticationSessionModel> clientCode = parsedCode.clientSessionCode;
|
||||
public Response cancelled() {
|
||||
AuthenticationSessionModel authSession = session.getContext().getAuthenticationSession();
|
||||
|
||||
Response accountManagementFailedLinking = checkAccountManagementFailedLinking(clientCode.getClientSession(), Messages.CONSENT_DENIED);
|
||||
Response accountManagementFailedLinking = checkAccountManagementFailedLinking(authSession, Messages.CONSENT_DENIED);
|
||||
if (accountManagementFailedLinking != null) {
|
||||
return accountManagementFailedLinking;
|
||||
}
|
||||
|
||||
return browserAuthentication(clientCode.getClientSession(), null);
|
||||
return browserAuthentication(authSession, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response error(String code, String message) {
|
||||
ParsedCodeContext parsedCode = parseEncodedSessionCode(code);
|
||||
if (parsedCode.response != null) {
|
||||
return parsedCode.response;
|
||||
}
|
||||
ClientSessionCode<AuthenticationSessionModel> clientCode = parsedCode.clientSessionCode;
|
||||
public Response error(String message) {
|
||||
AuthenticationSessionModel authSession = session.getContext().getAuthenticationSession();
|
||||
|
||||
Response accountManagementFailedLinking = checkAccountManagementFailedLinking(clientCode.getClientSession(), message);
|
||||
Response accountManagementFailedLinking = checkAccountManagementFailedLinking(authSession, message);
|
||||
if (accountManagementFailedLinking != null) {
|
||||
return accountManagementFailedLinking;
|
||||
}
|
||||
|
||||
Response passiveLoginErrorReturned = checkPassiveLoginError(clientCode.getClientSession(), message);
|
||||
Response passiveLoginErrorReturned = checkPassiveLoginError(authSession, message);
|
||||
if (passiveLoginErrorReturned != null) {
|
||||
return passiveLoginErrorReturned;
|
||||
}
|
||||
|
||||
return browserAuthentication(clientCode.getClientSession(), message);
|
||||
return browserAuthentication(authSession, message);
|
||||
}
|
||||
|
||||
|
||||
|
@ -1059,7 +1028,8 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
|||
}
|
||||
}
|
||||
|
||||
private ParsedCodeContext parseEncodedSessionCode(String encodedCode) {
|
||||
@Override
|
||||
public AuthenticationSessionModel getAndVerifyAuthenticationSession(String encodedCode) {
|
||||
IdentityBrokerState state = IdentityBrokerState.encoded(encodedCode);
|
||||
String code = state.getDecodedState();
|
||||
String clientId = state.getClientId();
|
||||
|
@ -1067,11 +1037,14 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
|||
return parseSessionCode(code, clientId, tabId);
|
||||
}
|
||||
|
||||
private ParsedCodeContext parseSessionCode(String code, String clientId, String tabId) {
|
||||
/**
|
||||
* This method will throw JAX-RS exception in case it is not able to retrieve AuthenticationSessionModel. It never returns null
|
||||
*/
|
||||
private AuthenticationSessionModel parseSessionCode(String code, String clientId, String tabId) {
|
||||
if (code == null || clientId == null || tabId == null) {
|
||||
logger.debugf("Invalid request. Authorization code, clientId or tabId was null. Code=%s, clientId=%s, tabID=%s", code, clientId, tabId);
|
||||
Response staleCodeError = redirectToErrorPage(Response.Status.BAD_REQUEST, Messages.INVALID_REQUEST);
|
||||
return ParsedCodeContext.response(staleCodeError);
|
||||
throw new WebApplicationException(staleCodeError);
|
||||
}
|
||||
|
||||
SessionCodeChecks checks = new SessionCodeChecks(realmModel, session.getContext().getUri(), request, clientConnection, session, event, null, code, null, clientId, tabId, LoginActionsService.AUTHENTICATE_PATH);
|
||||
|
@ -1083,59 +1056,26 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
|||
// Check if error happened during login or during linking from account management
|
||||
Response accountManagementFailedLinking = checkAccountManagementFailedLinking(authSession, Messages.STALE_CODE_ACCOUNT);
|
||||
if (accountManagementFailedLinking != null) {
|
||||
return ParsedCodeContext.response(accountManagementFailedLinking);
|
||||
throw new WebApplicationException(accountManagementFailedLinking);
|
||||
} else {
|
||||
Response errorResponse = checks.getResponse();
|
||||
|
||||
// Remove "code" from browser history
|
||||
errorResponse = BrowserHistoryHelper.getInstance().saveResponseAndRedirect(session, authSession, errorResponse, true, request);
|
||||
return ParsedCodeContext.response(errorResponse);
|
||||
throw new WebApplicationException(errorResponse);
|
||||
}
|
||||
} else {
|
||||
return ParsedCodeContext.response(checks.getResponse());
|
||||
throw new WebApplicationException(checks.getResponse());
|
||||
}
|
||||
} else {
|
||||
if (isDebugEnabled()) {
|
||||
logger.debugf("Authorization code is valid.");
|
||||
}
|
||||
|
||||
return ParsedCodeContext.clientSessionCode(checks.getClientCode());
|
||||
return checks.getClientCode().getClientSession();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If there is a client whose SAML IDP-initiated SSO URL name is set to the
|
||||
* given {@code clientUrlName}, creates a fresh client session for that
|
||||
* client and returns a {@link ParsedCodeContext} object with that session.
|
||||
* Otherwise returns "client not found" response.
|
||||
*
|
||||
* @param clientUrlName
|
||||
* @return see description
|
||||
*/
|
||||
private ParsedCodeContext samlIdpInitiatedSSO(final String clientUrlName) {
|
||||
event.event(EventType.LOGIN);
|
||||
CacheControlUtil.noBackButtonCacheControlHeader();
|
||||
Optional<ClientModel> oClient = this.realmModel.getClientsStream()
|
||||
.filter(c -> Objects.equals(c.getAttribute(SamlProtocol.SAML_IDP_INITIATED_SSO_URL_NAME), clientUrlName))
|
||||
.findFirst();
|
||||
|
||||
if (! oClient.isPresent()) {
|
||||
event.error(Errors.CLIENT_NOT_FOUND);
|
||||
return ParsedCodeContext.response(redirectToErrorPage(Response.Status.BAD_REQUEST, Messages.CLIENT_NOT_FOUND));
|
||||
}
|
||||
|
||||
LoginProtocolFactory factory = (LoginProtocolFactory) session.getKeycloakSessionFactory().getProviderFactory(LoginProtocol.class, SamlProtocol.LOGIN_PROTOCOL);
|
||||
SamlService samlService = (SamlService) factory.createProtocolEndpoint(realmModel, event);
|
||||
ResteasyProviderFactory.getInstance().injectProperties(samlService);
|
||||
AuthenticationSessionModel authSession = samlService.getOrCreateLoginSessionForIdpInitiatedSso(session, realmModel, oClient.get(), null);
|
||||
if (authSession == null) {
|
||||
event.error(Errors.INVALID_REDIRECT_URI);
|
||||
return ParsedCodeContext.response(redirectToErrorPage(Response.Status.BAD_REQUEST, Messages.INVALID_REDIRECT_URI));
|
||||
}
|
||||
|
||||
return ParsedCodeContext.clientSessionCode(new ClientSessionCode<>(session, this.realmModel, authSession));
|
||||
}
|
||||
|
||||
private Response checkAccountManagementFailedLinking(AuthenticationSessionModel authSession, String error, Object... parameters) {
|
||||
UserSessionModel userSession = new AuthenticationSessionManager(session).getUserSession(authSession);
|
||||
if (userSession != null && authSession.getClient() != null && authSession.getClient().getClientId().equals(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID)) {
|
||||
|
@ -1348,21 +1288,4 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
|||
}
|
||||
}
|
||||
|
||||
private static class ParsedCodeContext {
|
||||
private ClientSessionCode<AuthenticationSessionModel> clientSessionCode;
|
||||
private Response response;
|
||||
|
||||
public static ParsedCodeContext clientSessionCode(ClientSessionCode<AuthenticationSessionModel> clientSessionCode) {
|
||||
ParsedCodeContext ctx = new ParsedCodeContext();
|
||||
ctx.clientSessionCode = clientSessionCode;
|
||||
return ctx;
|
||||
}
|
||||
|
||||
public static ParsedCodeContext response(Response response) {
|
||||
ParsedCodeContext ctx = new ParsedCodeContext();
|
||||
ctx.response = response;
|
||||
return ctx;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -184,28 +184,27 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
|
|||
public Response authResponse(@QueryParam("state") String state,
|
||||
@QueryParam("denied") String denied,
|
||||
@QueryParam("oauth_verifier") String verifier) {
|
||||
if (denied != null) {
|
||||
return callback.cancelled(state);
|
||||
IdentityBrokerState idpState = IdentityBrokerState.encoded(state);
|
||||
String clientId = idpState.getClientId();
|
||||
String tabId = idpState.getTabId();
|
||||
if (clientId == null || tabId == null) {
|
||||
logger.errorf("Invalid state parameter: %s", state);
|
||||
sendErrorEvent();
|
||||
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.INVALID_REQUEST);
|
||||
}
|
||||
|
||||
ClientModel client = realm.getClientByClientId(clientId);
|
||||
AuthenticationSessionModel authSession = ClientSessionCode.getClientSession(state, tabId, session, realm, client, event, AuthenticationSessionModel.class);
|
||||
|
||||
if (denied != null) {
|
||||
return callback.cancelled();
|
||||
}
|
||||
|
||||
AuthenticationSessionModel authSession = null;
|
||||
try (VaultStringSecret vaultStringSecret = session.vault().getStringSecret(getConfig().getClientSecret())) {
|
||||
Twitter twitter = new TwitterFactory().getInstance();
|
||||
|
||||
twitter.setOAuthConsumer(getConfig().getClientId(), vaultStringSecret.get().orElse(getConfig().getClientSecret()));
|
||||
|
||||
IdentityBrokerState idpState = IdentityBrokerState.encoded(state);
|
||||
String clientId = idpState.getClientId();
|
||||
String tabId = idpState.getTabId();
|
||||
if (clientId == null || tabId == null) {
|
||||
logger.errorf("Invalid state parameter: %s", state);
|
||||
sendErrorEvent();
|
||||
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.INVALID_REQUEST);
|
||||
}
|
||||
|
||||
ClientModel client = realm.getClientByClientId(clientId);
|
||||
authSession = ClientSessionCode.getClientSession(state, tabId, session, realm, client, event, AuthenticationSessionModel.class);
|
||||
|
||||
String twitterToken = authSession.getAuthNote(TWITTER_TOKEN);
|
||||
String twitterSecret = authSession.getAuthNote(TWITTER_TOKENSECRET);
|
||||
|
||||
|
@ -236,7 +235,7 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
|
|||
identity.getContextData().put(IdentityProvider.FEDERATED_ACCESS_TOKEN, token);
|
||||
|
||||
identity.setIdpConfig(getConfig());
|
||||
identity.setCode(state);
|
||||
identity.setAuthenticationSession(authSession);
|
||||
|
||||
return callback.authenticated(identity);
|
||||
} catch (WebApplicationException e) {
|
||||
|
|
|
@ -0,0 +1,174 @@
|
|||
/*
|
||||
* Copyright 2020 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.broker;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
import org.keycloak.common.util.UriUtils;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.pages.AppPage;
|
||||
import org.keycloak.testsuite.pages.LoginExpiredPage;
|
||||
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.keycloak.testsuite.broker.BrokerTestTools.getConsumerRoot;
|
||||
import static org.keycloak.testsuite.broker.BrokerTestTools.waitForPage;
|
||||
|
||||
/**
|
||||
* Tests related to OIDC "state" parameter used in the OIDC AuthenticationResponse sent by the IDP to the SP endpoint
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class KcOidcBrokerStateParameterTest extends AbstractInitializedBaseBrokerTest {
|
||||
|
||||
@Page
|
||||
protected AppPage appPage;
|
||||
|
||||
@Page
|
||||
protected LoginExpiredPage loginExpiredPage;
|
||||
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(this);
|
||||
|
||||
@Override
|
||||
protected BrokerConfiguration getBrokerConfiguration() {
|
||||
return KcOidcBrokerConfiguration.INSTANCE;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMissingStateParameter() {
|
||||
final String consumerEndpointUrl = getURLOfOIDCIdpEndpointOnConsumerSide() + "?code=foo123";
|
||||
|
||||
events.clear();
|
||||
|
||||
// Manually open the consumer endpoint URL
|
||||
driver.navigate().to(consumerEndpointUrl);
|
||||
waitForPage(driver, "sign in to consumer", true);
|
||||
|
||||
errorPage.assertCurrent();
|
||||
assertThat(errorPage.getError(), Matchers.is("Missing state parameter in response from identity provider."));
|
||||
|
||||
// Test that only loginEvent happened on consumer side. There should *not* be request sent to provider realm codeToToken endpoint (Assert that event is not there)
|
||||
String consumerRealmId = realmsResouce().realm(bc.consumerRealmName()).toRepresentation().getId();
|
||||
events.expect(EventType.IDENTITY_PROVIDER_LOGIN_ERROR)
|
||||
.clearDetails()
|
||||
.session((String) null)
|
||||
.realm(consumerRealmId)
|
||||
.user((String) null)
|
||||
.client((String) null)
|
||||
.error("identity_provider_login_failure")
|
||||
.assertEvent();
|
||||
|
||||
events.assertEmpty();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testIncorrectStateParameter() throws Exception {
|
||||
final String consumerEndpointUrl = KeycloakUriBuilder.fromUri(getURLOfOIDCIdpEndpointOnConsumerSide())
|
||||
.queryParam(OAuth2Constants.CODE, "foo456")
|
||||
.queryParam(OAuth2Constants.STATE, "someIncorrectState")
|
||||
.build().toString();
|
||||
|
||||
events.clear();
|
||||
|
||||
// Manually open the consumer endpoint URL
|
||||
String consumerRealmId = realmsResouce().realm(bc.consumerRealmName()).toRepresentation().getId();
|
||||
driver.navigate().to(consumerEndpointUrl);
|
||||
|
||||
// Test that only loginEvent happened on consumer side. There should *not* be request sent to provider realm codeToToken endpoint (Assert that event is not there)
|
||||
events.expect(EventType.IDENTITY_PROVIDER_LOGIN_ERROR)
|
||||
.clearDetails()
|
||||
.session((String) null)
|
||||
.realm(consumerRealmId)
|
||||
.user((String) null)
|
||||
.client((String) null)
|
||||
.error("invalidRequestMessage")
|
||||
.assertEvent();
|
||||
|
||||
events.assertEmpty();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCorrectStateParameterButIncorrectCode() throws Exception {
|
||||
driver.navigate().to(getAccountUrl(getConsumerRoot(), bc.consumerRealmName()));
|
||||
|
||||
waitForPage(driver, "sign in to", true);
|
||||
loginPage.clickSocial(bc.getIDPAlias());
|
||||
waitForPage(driver, "sign in to", true);
|
||||
|
||||
// Get the "state", which was generated by "consumer" before sending OIDC AuthenticationRequest to "provider"
|
||||
String url = driver.getCurrentUrl();
|
||||
String stateParamValue = UriUtils.decodeQueryString(url).getFirst(OAuth2Constants.STATE);
|
||||
|
||||
final String consumerEndpointUrl = KeycloakUriBuilder.fromUri(getURLOfOIDCIdpEndpointOnConsumerSide())
|
||||
.queryParam(OAuth2Constants.CODE, "foo123")
|
||||
.queryParam(OAuth2Constants.STATE, stateParamValue)
|
||||
.build().toString();
|
||||
|
||||
events.clear();
|
||||
|
||||
// Manually open the consumer endpoint URL
|
||||
String providerRealmId = realmsResouce().realm(bc.providerRealmName()).toRepresentation().getId();
|
||||
String consumerRealmId = realmsResouce().realm(bc.consumerRealmName()).toRepresentation().getId();
|
||||
driver.navigate().to(consumerEndpointUrl);
|
||||
|
||||
// Check that loginError on consumer side was triggered. Also CodeToToken request was sent to the "provider", but failed due the incorrect code
|
||||
events.expect(EventType.CODE_TO_TOKEN_ERROR)
|
||||
.clearDetails()
|
||||
.session((String) null)
|
||||
.realm(providerRealmId)
|
||||
.user((String) null)
|
||||
.client("brokerapp")
|
||||
.error("invalid_code")
|
||||
.assertEvent();
|
||||
|
||||
events.expect(EventType.IDENTITY_PROVIDER_LOGIN_ERROR)
|
||||
.clearDetails()
|
||||
.session((String) null)
|
||||
.realm(consumerRealmId)
|
||||
.user((String) null)
|
||||
.client(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID)
|
||||
.error("identity_provider_login_failure")
|
||||
.assertEvent();
|
||||
|
||||
// Re-send the request to same URL. There should *not* be additional
|
||||
// request sent to provider realm codeToToken endpoint due the "state" already used on consumer side (Assert that CodeToToken event is not there)
|
||||
// The consumer should display "Page has expired" error
|
||||
driver.navigate().to(consumerEndpointUrl);
|
||||
loginExpiredPage.assertCurrent();
|
||||
|
||||
events.assertEmpty();
|
||||
|
||||
}
|
||||
|
||||
// Return the endpoint on consumer side where the IDentity Provider redirects the browser after successful authentication on IDP side.
|
||||
private String getURLOfOIDCIdpEndpointOnConsumerSide() {
|
||||
BrokerConfiguration brokerConfig = getBrokerConfiguration();
|
||||
return oauth.AUTH_SERVER_ROOT + "/realms/" + brokerConfig.consumerRealmName() + "/broker/" + brokerConfig.getIDPAlias() + "/endpoint";
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -413,52 +413,6 @@ public final class KcOidcBrokerTest extends AbstractAdvancedBrokerTest {
|
|||
errorPage.assertCurrent();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMissingStateParameter() {
|
||||
final String IDP_NAME = "github";
|
||||
|
||||
RealmResource realmResource = Optional.ofNullable(realmsResouce().realm(bc.providerRealmName())).orElse(null);
|
||||
assertThat(realmResource, Matchers.notNullValue());
|
||||
final int COUNT_PROVIDERS = Optional.of(realmResource.identityProviders().findAll().size()).orElse(0);
|
||||
|
||||
try {
|
||||
IdentityProviderRepresentation idp = new IdentityProviderRepresentation();
|
||||
idp.setAlias(IDP_NAME);
|
||||
idp.setDisplayName(IDP_NAME);
|
||||
idp.setProviderId(IDP_NAME);
|
||||
idp.setEnabled(true);
|
||||
|
||||
Response response = realmResource.identityProviders().create(idp);
|
||||
assertThat(response, Matchers.notNullValue());
|
||||
assertThat(response.getStatus(), Matchers.is(Response.Status.CREATED.getStatusCode()));
|
||||
assertThat(realmResource.identityProviders().findAll().size(), Matchers.is(COUNT_PROVIDERS + 1));
|
||||
assertThat(ApiUtil.getCreatedId(response), Matchers.notNullValue());
|
||||
|
||||
IdentityProviderRepresentation provider = Optional.ofNullable(realmResource.identityProviders().get(IDP_NAME).toRepresentation()).orElse(null);
|
||||
assertThat(provider, Matchers.notNullValue());
|
||||
assertThat(provider.getProviderId(), Matchers.is(IDP_NAME));
|
||||
assertThat(provider.getAlias(), Matchers.is(IDP_NAME));
|
||||
assertThat(provider.getDisplayName(), Matchers.is(IDP_NAME));
|
||||
|
||||
final String REALM_NAME = Optional.ofNullable(realmResource.toRepresentation().getRealm()).orElse(null);
|
||||
assertThat(REALM_NAME, Matchers.notNullValue());
|
||||
|
||||
final String LINK = oauth.AUTH_SERVER_ROOT + "/realms/" + REALM_NAME + "/broker/" + IDP_NAME + "/endpoint?code=foo123";
|
||||
|
||||
driver.navigate().to(LINK);
|
||||
waitForPage(driver, "sign in to provider", true);
|
||||
|
||||
errorPage.assertCurrent();
|
||||
assertThat(errorPage.getError(), Matchers.is("Missing state parameter in response from identity provider."));
|
||||
|
||||
} finally {
|
||||
IdentityProviderResource resource = Optional.ofNullable(realmResource.identityProviders().get(IDP_NAME)).orElse(null);
|
||||
assertThat(resource, Matchers.notNullValue());
|
||||
resource.remove();
|
||||
assertThat(Optional.of(realmResource.identityProviders().findAll().size()).orElse(0), Matchers.is(COUNT_PROVIDERS));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIdPNotFound() {
|
||||
final String notExistingIdP = "not-exists";
|
||||
|
|
Loading…
Reference in a new issue