LoginActionsService.java

1248 lines | 54.183 kB Blame History Raw Download
/*
 * 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.services.resources;

import org.jboss.logging.Logger;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.OAuth2Constants;
import org.keycloak.authentication.AuthenticationProcessor;
import org.keycloak.authentication.RequiredActionContext;
import org.keycloak.authentication.RequiredActionContextResult;
import org.keycloak.authentication.RequiredActionFactory;
import org.keycloak.authentication.RequiredActionProvider;
import org.keycloak.TokenVerifier;
import org.keycloak.TokenVerifier.Predicate;
import org.keycloak.authentication.ResetCredentialsActionToken;
import org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator;
import org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator;
import org.keycloak.common.ClientConnection;
import org.keycloak.common.VerificationException;
import org.keycloak.common.util.Time;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.ClientLoginSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserConsentModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.FormMessage;
import org.keycloak.protocol.LoginProtocol;
import org.keycloak.protocol.LoginProtocol.Error;
import org.keycloak.protocol.RestartLoginCookie;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.utils.OIDCResponseMode;
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
import org.keycloak.representations.JsonWebToken;
import org.keycloak.services.ErrorPage;
import org.keycloak.services.ServicesLogger;
import org.keycloak.services.Urls;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.managers.ClientSessionCode.ActionType;
import org.keycloak.services.managers.ClientSessionCode.ParseResult;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.util.CacheControlUtil;
import org.keycloak.services.util.CookieHelper;
import org.keycloak.sessions.CommonClientSessionModel;
import org.keycloak.sessions.LoginSessionModel;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.ext.Providers;
import java.net.URI;
import java.util.Objects;

/**
 * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
 */
public class LoginActionsService {

    private static final Logger logger = Logger.getLogger(LoginActionsService.class);

    public static final String ACTION_COOKIE = "KEYCLOAK_ACTION";
    public static final String AUTHENTICATE_PATH = "authenticate";
    public static final String REGISTRATION_PATH = "registration";
    public static final String RESET_CREDENTIALS_PATH = "reset-credentials";
    public static final String REQUIRED_ACTION = "required-action";
    public static final String FIRST_BROKER_LOGIN_PATH = "first-broker-login";
    public static final String POST_BROKER_LOGIN_PATH = "post-broker-login";
    public static final String LAST_PROCESSED_CODE = "last_processed_code";

    private RealmModel realm;

    @Context
    private HttpRequest request;

    @Context
    protected HttpHeaders headers;

    @Context
    private UriInfo uriInfo;

    @Context
    private ClientConnection clientConnection;

    @Context
    protected Providers providers;

    @Context
    protected KeycloakSession session;

    private EventBuilder event;

    public static UriBuilder loginActionsBaseUrl(UriInfo uriInfo) {
        UriBuilder baseUriBuilder = uriInfo.getBaseUriBuilder();
        return loginActionsBaseUrl(baseUriBuilder);
    }

    public static UriBuilder authenticationFormProcessor(UriInfo uriInfo) {
        return loginActionsBaseUrl(uriInfo).path(LoginActionsService.class, "authenticateForm");
    }

    public static UriBuilder requiredActionProcessor(UriInfo uriInfo) {
        return loginActionsBaseUrl(uriInfo).path(LoginActionsService.class, "requiredActionPOST");
    }

    public static UriBuilder registrationFormProcessor(UriInfo uriInfo) {
        return loginActionsBaseUrl(uriInfo).path(LoginActionsService.class, "processRegister");
    }

    public static UriBuilder firstBrokerLoginProcessor(UriInfo uriInfo) {
        return loginActionsBaseUrl(uriInfo).path(LoginActionsService.class, "firstBrokerLoginGet");
    }

    public static UriBuilder postBrokerLoginProcessor(UriInfo uriInfo) {
        return loginActionsBaseUrl(uriInfo).path(LoginActionsService.class, "postBrokerLoginGet");
    }

    public static UriBuilder loginActionsBaseUrl(UriBuilder baseUriBuilder) {
        return baseUriBuilder.path(RealmsResource.class).path(RealmsResource.class, "getLoginActionsService");
    }

    public LoginActionsService(RealmModel realm, EventBuilder event) {
        this.realm = realm;
        this.event = event;
        CacheControlUtil.noBackButtonCacheControlHeader();
    }

    private boolean checkSsl() {
        if (uriInfo.getBaseUri().getScheme().equals("https")) {
            return true;
        } else {
            return !realm.getSslRequired().isRequired(clientConnection);
        }
    }

    private <C extends CommonClientSessionModel> SessionCodeChecks<C> checksForCode(String code, Class<C> expectedClazz) {
        SessionCodeChecks<C> res = new SessionCodeChecks<>(code, expectedClazz);
        res.initialVerifyCode();
        return res;
    }



    private class SessionCodeChecks<C extends CommonClientSessionModel> {
        ClientSessionCode<C> clientCode;
        Response response;
        ClientSessionCode.ParseResult<C> result;
        Class<C> expectedClazz;

        private final String code;

        public SessionCodeChecks(String code, Class<C> expectedClazz) {
            this.code = code;
            this.expectedClazz = expectedClazz;
        }

        public C getClientSession() {
            return clientCode == null ? null : clientCode.getClientSession();
        }

        public boolean passed() {
            return response == null;
        }

        public boolean failed() {
            return response != null;
        }


        boolean verifyCode(String requiredAction, ClientSessionCode.ActionType actionType) {
            if (failed()) {
                return false;
            }

            if (!clientCode.isValidAction(requiredAction)) {
                C clientSession = getClientSession();
                if (ClientSessionModel.Action.REQUIRED_ACTIONS.name().equals(clientSession.getAction())) {
                    response = redirectToRequiredActions(code);
                    return false;
                } // TODO:mposolda
                /*else if (clientSession.getUserSession() != null && clientSession.getUserSession().getState() == UserSessionModel.State.LOGGED_IN) {
                    response = session.getProvider(LoginFormsProvider.class)
                            .setSuccess(Messages.ALREADY_LOGGED_IN)
                            .createInfoPage();
                    return false;
                }*/
            }

            return isActionActive(actionType);
        }

        private boolean isValidAction(String requiredAction) {
            if (!clientCode.isValidAction(requiredAction)) {
                invalidAction();
                return false;
            }
            return true;
        }

        private void invalidAction() {
            event.client(getClientSession().getClient());
            event.error(Errors.INVALID_CODE);
            response = ErrorPage.error(session, Messages.INVALID_CODE);
        }

        private boolean isActionActive(ClientSessionCode.ActionType actionType) {
            if (!clientCode.isActionActive(actionType)) {
                event.client(getClientSession().getClient());
                event.clone().error(Errors.EXPIRED_CODE);
                if (getClientSession().getAction().equals(ClientSessionModel.Action.AUTHENTICATE.name())) {
                    LoginSessionModel loginSession = (LoginSessionModel) getClientSession();
                    AuthenticationProcessor.resetFlow(loginSession);
                    response = processAuthentication(null, loginSession, Messages.LOGIN_TIMEOUT);
                    return false;
                }
                response = ErrorPage.error(session, Messages.EXPIRED_CODE);
                return false;
            }
            return true;
        }

        private boolean initialVerifyCode() {
            if (!checkSsl()) {
                event.error(Errors.SSL_REQUIRED);
                response = ErrorPage.error(session, Messages.HTTPS_REQUIRED);
                return false;
            }
            if (!realm.isEnabled()) {
                event.error(Errors.REALM_DISABLED);
                response = ErrorPage.error(session, Messages.REALM_NOT_ENABLED);
                return false;
            }
            result = ClientSessionCode.parseResult(code, session, realm, expectedClazz);
            clientCode = result.getCode();
            if (clientCode == null) {
                if (result.isLoginSessionNotFound()) { // timeout or loginSession already logged
                    // TODO:mposolda
                    /*
                    try {
                        ClientSessionModel clientSession = RestartLoginCookie.restartSession(session, realm, code);
                        if (clientSession != null) {
                            event.clone().detail(Details.RESTART_AFTER_TIMEOUT, "true").error(Errors.EXPIRED_CODE);
                            response = processFlow(null, clientSession, AUTHENTICATE_PATH, realm.getBrowserFlow(), Messages.LOGIN_TIMEOUT, new AuthenticationProcessor());
                            return false;
                        }
                    } catch (Exception e) {
                        ServicesLogger.LOGGER.failedToParseRestartLoginCookie(e);
                    }*/
                }
                event.error(Errors.INVALID_CODE);
                response = ErrorPage.error(session, Messages.INVALID_CODE);
                return false;
            }

            C clientSession = getClientSession();
            if (clientSession == null) {
                event.error(Errors.INVALID_CODE);
                response = ErrorPage.error(session, Messages.INVALID_CODE);
                return false;
            }
            event.detail(Details.CODE_ID, clientSession.getId());
            ClientModel client = clientSession.getClient();
            if (client == null) {
                event.error(Errors.CLIENT_NOT_FOUND);
                response = ErrorPage.error(session, Messages.UNKNOWN_LOGIN_REQUESTER);
                // TODO:mposolda
                //session.sessions().removeClientSession(realm, clientSession);
                return false;
            }
            if (!client.isEnabled()) {
                event.error(Errors.CLIENT_NOT_FOUND);
                response = ErrorPage.error(session, Messages.LOGIN_REQUESTER_NOT_ENABLED);
                // TODO:mposolda
                //session.sessions().removeClientSession(realm, clientSession);
                return false;
            }
            session.getContext().setClient(client);
            return true;
        }

        public boolean verifyRequiredAction(String executedAction) {
            if (failed()) {
                return false;
            }

            if (!isValidAction(ClientSessionModel.Action.REQUIRED_ACTIONS.name())) return false;
            if (!isActionActive(ClientSessionCode.ActionType.USER)) return false;

            final LoginSessionModel  loginSession = (LoginSessionModel) getClientSession();

            if (executedAction == null) { // do next required action only if user is already authenticated
                initLoginEvent(loginSession);
                event.event(EventType.LOGIN);
                response = AuthenticationManager.nextActionAfterAuthentication(session, loginSession, clientConnection, request, uriInfo, event);
                return false;
            }

            if (!executedAction.equals(loginSession.getNote(AuthenticationManager.CURRENT_REQUIRED_ACTION))) {
                logger.debug("required action doesn't match current required action");
                loginSession.removeNote(AuthenticationManager.CURRENT_REQUIRED_ACTION);
                response = redirectToRequiredActions(code);
                return false;
            }
            return true;
        }
    }

    /**
     * protocol independent login page entry point
     *
     * @param code
     * @return
     */
    @Path(AUTHENTICATE_PATH)
    @GET
    public Response authenticate(@QueryParam("code") String code,
                                 @QueryParam("execution") String execution) {
        event.event(EventType.LOGIN);

        LoginSessionModel loginSession = ClientSessionCode.getClientSession(code, session, realm, LoginSessionModel.class);
        if (loginSession != null && code.equals(loginSession.getNote(LAST_PROCESSED_CODE))) {
            // Allow refresh of previous page
        } else {
            SessionCodeChecks<LoginSessionModel> checks = checksForCode(code, LoginSessionModel.class);
            if (!checks.verifyCode(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
                return checks.response;
            }

            ClientSessionCode<LoginSessionModel> clientSessionCode = checks.clientCode;
            loginSession = clientSessionCode.getClientSession();
        }

        event.detail(Details.CODE_ID, code);
        loginSession.setNote(LAST_PROCESSED_CODE, code);
        return processAuthentication(execution, loginSession, null);
    }

    protected Response processAuthentication(String execution, LoginSessionModel loginSession, String errorMessage) {
        return processFlow(execution, loginSession, AUTHENTICATE_PATH, realm.getBrowserFlow(), errorMessage, new AuthenticationProcessor());
    }

    protected Response processFlow(String execution, LoginSessionModel loginSession, String flowPath, AuthenticationFlowModel flow, String errorMessage, AuthenticationProcessor processor) {
        processor.setLoginSession(loginSession)
                .setFlowPath(flowPath)
                .setBrowserFlow(true)
                .setFlowId(flow.getId())
                .setConnection(clientConnection)
                .setEventBuilder(event)
                .setRealm(realm)
                .setSession(session)
                .setUriInfo(uriInfo)
                .setRequest(request);
        if (errorMessage != null) processor.setForwardedErrorMessage(new FormMessage(null, errorMessage));

        try {
            if (execution != null) {
                return processor.authenticationAction(execution);
            } else {
                return processor.authenticate();
            }
        } catch (Exception e) {
            return processor.handleBrowserException(e);
        }
    }

    /**
     * URL called after login page.  YOU SHOULD NEVER INVOKE THIS DIRECTLY!
     *
     * @param code
     * @return
     */
    @Path(AUTHENTICATE_PATH)
    @POST
    public Response authenticateForm(@QueryParam("code") String code,
                                     @QueryParam("execution") String execution) {
        event.event(EventType.LOGIN);

        LoginSessionModel loginSession = ClientSessionCode.getClientSession(code, session, realm, LoginSessionModel.class);
        if (loginSession != null && code.equals(loginSession.getNote(LAST_PROCESSED_CODE))) {
            // Post already processed (refresh) - ignore form post and return next form
            request.getFormParameters().clear();
            return authenticate(code, null);
        }

        SessionCodeChecks<LoginSessionModel> checks = checksForCode(code, LoginSessionModel.class);
        if (!checks.verifyCode(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
            return checks.response;
        }
        final ClientSessionCode<LoginSessionModel> clientCode = checks.clientCode;
        loginSession = clientCode.getClientSession();
        loginSession.setNote(LAST_PROCESSED_CODE, code);

        return processAuthentication(execution, loginSession, null);
    }

    @Path(RESET_CREDENTIALS_PATH)
    @POST
    public Response resetCredentialsPOST(@QueryParam("code") String code,
                                         @QueryParam("execution") String execution) {
        // TODO:mposolda
        //return resetCredentials(code, execution);
        return null;
    }

    private boolean isSslUsed(JsonWebToken t) throws VerificationException {
        if (! checkSsl()) {
            event.error(Errors.SSL_REQUIRED);
            throw new LoginActionsServiceException(ErrorPage.error(session, Messages.HTTPS_REQUIRED));
        }
        return true;
    }

    private boolean isRealmEnabled(JsonWebToken t) throws VerificationException {
        if (! realm.isEnabled()) {
            event.error(Errors.REALM_DISABLED);
            throw new LoginActionsServiceException(ErrorPage.error(session, Messages.REALM_NOT_ENABLED));
        }
        return true;
    }

    private boolean isResetCredentialsAllowed(ResetCredentialsActionToken t) throws VerificationException {
        if (!realm.isResetPasswordAllowed()) {
            event.client(t.getClientSession().getClient());
            event.error(Errors.NOT_ALLOWED);
            throw new LoginActionsServiceException(ErrorPage.error(session, Messages.RESET_CREDENTIAL_NOT_ALLOWED));
        }
        return true;
    }

    private boolean canResolveClientSession(ResetCredentialsActionToken t) throws VerificationException {
        // TODO:mposolda
        /*
        String clientSessionId = t == null ? null : t.getNote(ResetCredentialsActionToken.NOTE_CLIENT_SESSION_ID);

        if (t == null || clientSessionId == null) {
            event.error(Errors.INVALID_CODE);
            throw new LoginActionsServiceException(ErrorPage.error(session, Messages.INVALID_CODE));
        }

        ClientSessionModel clientSession = session.sessions().getClientSession(clientSessionId);
        t.setClientSession(clientSession);

        if (clientSession == null) { // timeout
            try {
                clientSession = RestartLoginCookie.restartSessionByClientSession(session, realm, clientSessionId);
            } catch (Exception e) {
                ServicesLogger.LOGGER.failedToParseRestartLoginCookie(e);
            }

            if (clientSession != null) {
                event.clone().detail(Details.RESTART_AFTER_TIMEOUT, "true").error(Errors.EXPIRED_CODE);
                throw new LoginActionsServiceException(processFlow(null, clientSession, AUTHENTICATE_PATH, realm.getBrowserFlow(), Messages.LOGIN_TIMEOUT, new AuthenticationProcessor()));
            }
        }

        if (clientSession == null) {
            event.error(Errors.INVALID_CODE);
            throw new LoginActionsServiceException(ErrorPage.error(session, Messages.INVALID_CODE));
        }

        event.detail(Details.CODE_ID, clientSession.getId());*/

        return true;
    }

    private boolean canResolveClient(ResetCredentialsActionToken t) throws VerificationException {
        ClientModel client = t.getClientSession().getClient();
        if (client == null) {
            event.error(Errors.CLIENT_NOT_FOUND);
            session.sessions().removeClientSession(realm, t.getClientSession());
            throw new LoginActionsServiceException(ErrorPage.error(session, Messages.UNKNOWN_LOGIN_REQUESTER));
        }

        if (! client.isEnabled()) {
            event.error(Errors.CLIENT_NOT_FOUND);
            session.sessions().removeClientSession(realm, t.getClientSession());
            throw new LoginActionsServiceException(ErrorPage.error(session, Messages.LOGIN_REQUESTER_NOT_ENABLED));
        }
        session.getContext().setClient(client);

        return true;
    }

    private class IsValidAction implements Predicate<ResetCredentialsActionToken> {

        private final String requiredAction;

        public IsValidAction(String requiredAction) {
            this.requiredAction = requiredAction;
        }
        
        @Override
        public boolean test(ResetCredentialsActionToken t) throws VerificationException {
            ClientSessionModel clientSession = t.getClientSession();
            if (! Objects.equals(clientSession.getAction(), this.requiredAction)) {

                if (ClientSessionModel.Action.REQUIRED_ACTIONS.name().equals(clientSession.getAction())) {
// TODO: Once login tokens would be implemented, this would have to be rewritten
//                    String code = clientSession.getNote(ClientSessionCode.ACTIVE_CODE) + "." + clientSession.getId();
                    String code = clientSession.getNote("active_code") + "." + clientSession.getId();
                    throw new LoginActionsServiceException(redirectToRequiredActions(code));
                } else if (clientSession.getUserSession() != null && clientSession.getUserSession().getState() == UserSessionModel.State.LOGGED_IN) {
                    throw new LoginActionsServiceException(
                      session.getProvider(LoginFormsProvider.class)
                            .setSuccess(Messages.ALREADY_LOGGED_IN)
                            .createInfoPage());
                 }
            }

            return true;
        }
    }

    private class IsActiveAction implements Predicate<ResetCredentialsActionToken> {
        private final ClientSessionCode.ActionType actionType;

        public IsActiveAction(ActionType actionType) {
            this.actionType = actionType;
        }

        @Override
        public boolean test(ResetCredentialsActionToken t) throws VerificationException {
            int timestamp = t.getClientSession().getTimestamp();
            if (! isActionActive(actionType, timestamp)) {
                event.client(t.getClientSession().getClient());
                event.clone().error(Errors.EXPIRED_CODE);

                if (t.getClientSession().getAction().equals(ClientSessionModel.Action.AUTHENTICATE.name())) {
                    // TODO:mposolda incompatible types
                    LoginSessionModel loginSession = (LoginSessionModel) t.getClientSession();

                    AuthenticationProcessor.resetFlow(loginSession);
                    throw new LoginActionsServiceException(processAuthentication(null, loginSession, Messages.LOGIN_TIMEOUT));
                }

                throw new LoginActionsServiceException(ErrorPage.error(session, Messages.EXPIRED_CODE));
            }
            return true;
        }

        public boolean isActionActive(ActionType actionType, int timestamp) {
            int lifespan;
            switch (actionType) {
                case CLIENT:
                    lifespan = realm.getAccessCodeLifespan();
                    break;
                case LOGIN:
                    lifespan = realm.getAccessCodeLifespanLogin() > 0 ? realm.getAccessCodeLifespanLogin() : realm.getAccessCodeLifespanUserAction();
                    break;
                case USER:
                    lifespan = realm.getAccessCodeLifespanUserAction();
                    break;
                default:
                    throw new IllegalArgumentException();
            }

            return timestamp + lifespan > Time.currentTime();
        }

    }

    /**
     * Endpoint for executing reset credentials flow.  If code is null, a client session is created with the account
     * service as the client.  Successful reset sends you to the account page.  Note, account service must be enabled.
     *
     * @param code
     * @param execution
     * @return
     */
    @Path(RESET_CREDENTIALS_PATH)
    @GET
    public Response resetCredentialsGET(@QueryParam("code") String code,
                                        @QueryParam("execution") String execution,
                                        @QueryParam("key") String key) {
        // we allow applications to link to reset credentials without going through OAuth or SAML handshakes
        if (code == null && key == null) {
            if (!realm.isResetPasswordAllowed()) {
                event.event(EventType.RESET_PASSWORD);
                event.error(Errors.NOT_ALLOWED);
                return ErrorPage.error(session, Messages.RESET_CREDENTIAL_NOT_ALLOWED);

            }
            // set up the account service as the endpoint to call.
            ClientModel client = realm.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
            ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
            clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
            //clientSession.setNote(AuthenticationManager.END_AFTER_REQUIRED_ACTIONS, "true");
            clientSession.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
            String redirectUri = Urls.accountBase(uriInfo.getBaseUri()).path("/").build(realm.getName()).toString();
            clientSession.setRedirectUri(redirectUri);
            clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
            clientSession.setNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM, OAuth2Constants.CODE);
            clientSession.setNote(OIDCLoginProtocol.REDIRECT_URI_PARAM, redirectUri);
            clientSession.setNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()));
            return processResetCredentials(null, clientSession, null);
        }

        if (key != null) {
            try {
                ResetCredentialsActionToken token = ResetCredentialsActionToken.deserialize(
                  session, realm, session.getContext().getUri(), key);
                return resetCredentials(code, token, execution);
            } catch (VerificationException ex) {
                event.event(EventType.RESET_PASSWORD)
                  .detail(Details.REASON, ex.getMessage())
                  .error(Errors.NOT_ALLOWED);
                return ErrorPage.error(session, Messages.RESET_CREDENTIAL_NOT_ALLOWED);
            }
        }
        
        return resetCredentials(code, execution);
    }


    /**
     * @deprecated In favor of {@link #resetCredentials(String, org.keycloak.authentication.ResetCredentialsActionToken, java.lang.String)}
     * @param code
     * @param execution
     * @return
     */
    protected Response resetCredentials(String code, String execution) {
        event.event(EventType.RESET_PASSWORD);
        SessionCodeChecks<LoginSessionModel> checks = checksForCode(code, LoginSessionModel.class);
        if (!checks.verifyCode(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.USER)) {
            return checks.response;
        }
        final LoginSessionModel clientSession = checks.getClientSession();

        if (!realm.isResetPasswordAllowed()) {
            event.client(clientSession.getClient());
            event.error(Errors.NOT_ALLOWED);
            return ErrorPage.error(session, Messages.RESET_CREDENTIAL_NOT_ALLOWED);

        }

        // TODO:mposolda
        //return processResetCredentials(execution, clientSession, null);
        return null;
    }

    protected Response resetCredentials(String code, ResetCredentialsActionToken token, String execution) {
        event.event(EventType.RESET_PASSWORD);

        if (token == null) {
            // TODO: Use more appropriate code
            event.error(Errors.NOT_ALLOWED);
            return ErrorPage.error(session, Messages.RESET_CREDENTIAL_NOT_ALLOWED);
        }

        try {
            TokenVerifier.from(token).checkOnly(
              // Start basic checks
              this::isRealmEnabled,
              this::isSslUsed,
              this::isResetCredentialsAllowed,
              this::canResolveClientSession,
              this::canResolveClient,
              // End basic checks
              
              new IsValidAction(ClientSessionModel.Action.AUTHENTICATE.name()),
              new IsActiveAction(ActionType.USER)
            ).verify();
        } catch (LoginActionsServiceException ex) {
            if (ex.getResponse() == null) {
                event.event(EventType.RESET_PASSWORD)
                  .detail(Details.REASON, ex.getMessage())
                  .error(Errors.INVALID_REQUEST);
                return ErrorPage.error(session, Messages.RESET_CREDENTIAL_NOT_ALLOWED);
            } else {
                return ex.getResponse();
            }
        } catch (VerificationException ex) {
            event.event(EventType.RESET_PASSWORD)
              .detail(Details.REASON, ex.getMessage())
              .error(Errors.NOT_ALLOWED);
            return ErrorPage.error(session, Messages.RESET_CREDENTIAL_NOT_ALLOWED);
        }

        final ClientSessionModel clientSession = token.getClientSession();

        return processResetCredentials(execution, clientSession, null);
    }

    protected Response processResetCredentials(String execution, ClientSessionModel clientSession, String errorMessage) {
        // TODO:mposolda
        /*
        AuthenticationProcessor authProcessor = new AuthenticationProcessor() {

            @Override
            protected Response authenticationComplete() {
                boolean firstBrokerLoginInProgress = (clientSession.getNote(AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE) != null);
                if (firstBrokerLoginInProgress) {

                    UserModel linkingUser = AbstractIdpAuthenticator.getExistingUser(session, realm, clientSession);
                    if (!linkingUser.getId().equals(clientSession.getAuthenticatedUser().getId())) {
                        return ErrorPage.error(session, Messages.IDENTITY_PROVIDER_DIFFERENT_USER_MESSAGE, clientSession.getAuthenticatedUser().getUsername(), linkingUser.getUsername());
                    }

                    logger.debugf("Forget-password flow finished when authenticated user '%s' after first broker login.", linkingUser.getUsername());

                    return redirectToAfterBrokerLoginEndpoint(clientSession, true);
                } else {
                    return super.authenticationComplete();
                }
            }
        };

        return processFlow(execution, clientSession, RESET_CREDENTIALS_PATH, realm.getResetCredentialsFlow(), errorMessage, authProcessor);
        */
        return null;
    }


    protected Response processRegistration(String execution, LoginSessionModel loginSession, String errorMessage) {
        return processFlow(execution, loginSession, REGISTRATION_PATH, realm.getRegistrationFlow(), errorMessage, new AuthenticationProcessor());
    }


    /**
     * protocol independent registration page entry point
     *
     * @param code
     * @return
     */
    @Path(REGISTRATION_PATH)
    @GET
    public Response registerPage(@QueryParam("code") String code,
                                 @QueryParam("execution") String execution) {
        event.event(EventType.REGISTER);
        if (!realm.isRegistrationAllowed()) {
            event.error(Errors.REGISTRATION_DISABLED);
            return ErrorPage.error(session, Messages.REGISTRATION_NOT_ALLOWED);
        }

        SessionCodeChecks<LoginSessionModel> checks = checksForCode(code, LoginSessionModel.class);
        if (!checks.verifyCode(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
            return checks.response;
        }
        event.detail(Details.CODE_ID, code);
        ClientSessionCode<LoginSessionModel> clientSessionCode = checks.clientCode;
        LoginSessionModel clientSession = clientSessionCode.getClientSession();


        AuthenticationManager.expireIdentityCookie(realm, uriInfo, clientConnection);

        return processRegistration(execution, clientSession, null);
    }


    /**
     * Registration
     *
     * @param code
     * @return
     */
    @Path(REGISTRATION_PATH)
    @POST
    public Response processRegister(@QueryParam("code") String code,
                                    @QueryParam("execution") String execution) {
        event.event(EventType.REGISTER);
        if (!realm.isRegistrationAllowed()) {
            event.error(Errors.REGISTRATION_DISABLED);
            return ErrorPage.error(session, Messages.REGISTRATION_NOT_ALLOWED);
        }
        SessionCodeChecks checks = checksForCode(code, LoginSessionModel.class);
        if (!checks.verifyCode(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
            return checks.response;
        }


        ClientSessionCode<LoginSessionModel> clientCode = checks.clientCode;
        LoginSessionModel loginSession = clientCode.getClientSession();
        return processRegistration(execution, loginSession, null);
    }

    // TODO:mposolda broker login
/*
    @Path(FIRST_BROKER_LOGIN_PATH)
    @GET
    public Response firstBrokerLoginGet(@QueryParam("code") String code,
                                 @QueryParam("execution") String execution) {
        return brokerLoginFlow(code, execution, true);
    }

    @Path(FIRST_BROKER_LOGIN_PATH)
    @POST
    public Response firstBrokerLoginPost(@QueryParam("code") String code,
                                        @QueryParam("execution") String execution) {
        return brokerLoginFlow(code, execution, true);
    }

    @Path(POST_BROKER_LOGIN_PATH)
    @GET
    public Response postBrokerLoginGet(@QueryParam("code") String code,
                                       @QueryParam("execution") String execution) {
        return brokerLoginFlow(code, execution, false);
    }

    @Path(POST_BROKER_LOGIN_PATH)
    @POST
    public Response postBrokerLoginPost(@QueryParam("code") String code,
                                        @QueryParam("execution") String execution) {
        return brokerLoginFlow(code, execution, false);
    }


    protected Response brokerLoginFlow(String code, String execution, final boolean firstBrokerLogin) {
        EventType eventType = firstBrokerLogin ? EventType.IDENTITY_PROVIDER_FIRST_LOGIN : EventType.IDENTITY_PROVIDER_POST_LOGIN;
        event.event(eventType);

        SessionCodeChecks checks = checksForCode(code);
        if (!checks.verifyCode(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
            return checks.response;
        }
        event.detail(Details.CODE_ID, code);
        final ClientSessionModel clientSessionn = checks.getClientSession();

        String noteKey = firstBrokerLogin ? AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE : PostBrokerLoginConstants.PBL_BROKERED_IDENTITY_CONTEXT;
        SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromClientSession(clientSessionn, noteKey);
        if (serializedCtx == null) {
            ServicesLogger.LOGGER.notFoundSerializedCtxInClientSession(noteKey);
            throw new WebApplicationException(ErrorPage.error(session, "Not found serialized context in clientSession."));
        }
        BrokeredIdentityContext brokerContext = serializedCtx.deserialize(session, clientSessionn);
        final String identityProviderAlias = brokerContext.getIdpConfig().getAlias();

        String flowId = firstBrokerLogin ? brokerContext.getIdpConfig().getFirstBrokerLoginFlowId() : brokerContext.getIdpConfig().getPostBrokerLoginFlowId();
        if (flowId == null) {
            ServicesLogger.LOGGER.flowNotConfigForIDP(identityProviderAlias);
            throw new WebApplicationException(ErrorPage.error(session, "Flow not configured for identity provider"));
        }
        AuthenticationFlowModel brokerLoginFlow = realm.getAuthenticationFlowById(flowId);
        if (brokerLoginFlow == null) {
            ServicesLogger.LOGGER.flowNotFoundForIDP(flowId, identityProviderAlias);
            throw new WebApplicationException(ErrorPage.error(session, "Flow not found for identity provider"));
        }

        event.detail(Details.IDENTITY_PROVIDER, identityProviderAlias)
                .detail(Details.IDENTITY_PROVIDER_USERNAME, brokerContext.getUsername());


        AuthenticationProcessor processor = new AuthenticationProcessor() {

            @Override
            protected Response authenticationComplete() {
                if (!firstBrokerLogin) {
                    String authStateNoteKey = PostBrokerLoginConstants.PBL_AUTH_STATE_PREFIX + identityProviderAlias;
                    clientSessionn.setNote(authStateNoteKey, "true");
                }

                return redirectToAfterBrokerLoginEndpoint(clientSession, firstBrokerLogin);
            }

        };

        String flowPath = firstBrokerLogin ? FIRST_BROKER_LOGIN_PATH : POST_BROKER_LOGIN_PATH;
        return processFlow(execution, clientSessionn, flowPath, brokerLoginFlow, null, processor);
    }

    private Response redirectToAfterBrokerLoginEndpoint(ClientSessionModel clientSession, boolean firstBrokerLogin) {
        ClientSessionCode accessCode = new ClientSessionCode(session, realm, clientSession);
        clientSession.setTimestamp(Time.currentTime());

        URI redirect = firstBrokerLogin ? Urls.identityProviderAfterFirstBrokerLogin(uriInfo.getBaseUri(), realm.getName(), accessCode.getCode()) :
                Urls.identityProviderAfterPostBrokerLogin(uriInfo.getBaseUri(), realm.getName(), accessCode.getCode()) ;
        logger.debugf("Redirecting to '%s' ", redirect);

        return Response.status(302).location(redirect).build();
    }
*/

    /**
     * OAuth grant page.  You should not invoked this directly!
     *
     * @param formData
     * @return
     */
    @Path("consent")
    @POST
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    public Response processConsent(final MultivaluedMap<String, String> formData) {
        event.event(EventType.LOGIN);
        String code = formData.getFirst("code");
        SessionCodeChecks<LoginSessionModel> checks = checksForCode(code, LoginSessionModel.class);
        if (!checks.verifyRequiredAction(ClientSessionModel.Action.OAUTH_GRANT.name())) {
            return checks.response;
        }

        ClientSessionCode<LoginSessionModel> accessCode = checks.clientCode;
        LoginSessionModel loginSession = accessCode.getClientSession();

        initLoginEvent(loginSession);

        UserModel user = loginSession.getAuthenticatedUser();
        ClientModel client = loginSession.getClient();


        if (formData.containsKey("cancel")) {
            LoginProtocol protocol = session.getProvider(LoginProtocol.class, loginSession.getProtocol());
            protocol.setRealm(realm)
                    .setHttpHeaders(headers)
                    .setUriInfo(uriInfo)
                    .setEventBuilder(event);
            Response response = protocol.sendError(loginSession, Error.CONSENT_DENIED);
            event.error(Errors.REJECTED_BY_USER);
            return response;
        }

        UserConsentModel grantedConsent = session.users().getConsentByClient(realm, user.getId(), client.getId());
        if (grantedConsent == null) {
            grantedConsent = new UserConsentModel(client);
            session.users().addConsent(realm, user.getId(), grantedConsent);
        }
        for (RoleModel role : accessCode.getRequestedRoles()) {
            grantedConsent.addGrantedRole(role);
        }
        for (ProtocolMapperModel protocolMapper : accessCode.getRequestedProtocolMappers()) {
            if (protocolMapper.isConsentRequired() && protocolMapper.getConsentText() != null) {
                grantedConsent.addGrantedProtocolMapper(protocolMapper);
            }
        }
        session.users().updateConsent(realm, user.getId(), grantedConsent);

        event.detail(Details.CONSENT, Details.CONSENT_VALUE_CONSENT_GRANTED);
        event.success();

        // TODO:mposolda So assume that requiredActions were already done in this stage. Doublecheck...
        ClientLoginSessionModel clientSession = AuthenticationProcessor.attachSession(loginSession, null, session, realm, clientConnection, event);
        return AuthenticationManager.redirectAfterSuccessfulFlow(session, realm, clientSession.getUserSession(), clientSession, request, uriInfo, clientConnection, event, loginSession.getProtocol());
    }

    @Path("email-verification")
    @GET
    public Response emailVerification(@QueryParam("code") String code, @QueryParam("key") String key) {
        // TODO:mposolda
        /*
        event.event(EventType.VERIFY_EMAIL);
        if (key != null) {
            ClientSessionModel clientSession = null;
            String keyFromSession = null;
            if (code != null) {
                clientSession = ClientSessionCode.getClientSession(code, session, realm);
                keyFromSession = clientSession != null ? clientSession.getNote(Constants.VERIFY_EMAIL_KEY) : null;
            }

            if (!key.equals(keyFromSession)) {
                ServicesLogger.LOGGER.invalidKeyForEmailVerification();
                event.error(Errors.INVALID_CODE);
                throw new WebApplicationException(ErrorPage.error(session, Messages.STALE_VERIFY_EMAIL_LINK));
            }

            clientSession.removeNote(Constants.VERIFY_EMAIL_KEY);

            SessionCodeChecks checks = checksForCode(code);
            if (!checks.verifyCode(ClientSessionModel.Action.REQUIRED_ACTIONS.name(), ClientSessionCode.ActionType.USER)) {
                if (checks.clientCode == null && checks.result.isClientSessionNotFound() || checks.result.isIllegalHash()) {
                   return ErrorPage.error(session, Messages.STALE_VERIFY_EMAIL_LINK);
                }
                return checks.response;
            }

            clientSession = checks.getClientSession();
            if (!ClientSessionModel.Action.VERIFY_EMAIL.name().equals(clientSession.getNote(AuthenticationManager.CURRENT_REQUIRED_ACTION))) {
                ServicesLogger.LOGGER.reqdActionDoesNotMatch();
                event.error(Errors.INVALID_CODE);
                throw new WebApplicationException(ErrorPage.error(session, Messages.STALE_VERIFY_EMAIL_LINK));
            }

            UserSessionModel userSession = clientSession.getUserSession();
            UserModel user = userSession.getUser();
            initEvent(clientSession);
            event.event(EventType.VERIFY_EMAIL).detail(Details.EMAIL, user.getEmail());

            user.setEmailVerified(true);

            user.removeRequiredAction(RequiredAction.VERIFY_EMAIL);

            event.success();

            String actionCookieValue = getActionCookie();
            if (actionCookieValue == null || !actionCookieValue.equals(userSession.getId())) {
                session.sessions().removeClientSession(realm, clientSession);
                return session.getProvider(LoginFormsProvider.class)
                        .setSuccess(Messages.EMAIL_VERIFIED)
                        .createInfoPage();
            }

            event = event.clone().removeDetail(Details.EMAIL).event(EventType.LOGIN);

            return AuthenticationProcessor.redirectToRequiredActions(session, realm, clientSession, uriInfo);
        } else {
            SessionCodeChecks checks = checksForCode(code);
            if (!checks.verifyCode(ClientSessionModel.Action.REQUIRED_ACTIONS.name(), ClientSessionCode.ActionType.USER)) {
                return checks.response;
            }
            ClientSessionCode accessCode = checks.clientCode;
            ClientSessionModel clientSession = checks.getClientSession();
            UserSessionModel userSession = clientSession.getUserSession();
            initEvent(clientSession);

            createActionCookie(realm, uriInfo, clientConnection, userSession.getId());

            VerifyEmail.setupKey(clientSession);

            return session.getProvider(LoginFormsProvider.class)
                    .setClientSessionCode(accessCode.getCode())
                    .setClientSession(clientSession)
                    .setUser(userSession.getUser())
                    .createResponse(RequiredAction.VERIFY_EMAIL);
        }*/
        return null;
    }

    /**
     * Initiated by admin, not the user on login
     *
     * @param key
     * @return
     */
    @Path("execute-actions")
    @GET
    public Response executeActions(@QueryParam("key") String key) {
        // TODO:mposolda
        /*
        event.event(EventType.EXECUTE_ACTIONS);
        if (key != null) {
            SessionCodeChecks checks = checksForCode(key);
            if (!checks.verifyCode(ClientSessionModel.Action.EXECUTE_ACTIONS.name(), ClientSessionCode.ActionType.USER)) {
                return checks.response;
            }
            ClientSessionModel clientSession = checks.getClientSession();
            // verify user email as we know it is valid as this entry point would never have gotten here.
            clientSession.getUserSession().getUser().setEmailVerified(true);
            clientSession.setNote(AuthenticationManager.END_AFTER_REQUIRED_ACTIONS, "true");
            clientSession.setNote(ClientSessionModel.Action.EXECUTE_ACTIONS.name(), "true");
            return AuthenticationProcessor.redirectToRequiredActions(session, realm, clientSession, uriInfo);
        } else {
            event.error(Errors.INVALID_CODE);
            return ErrorPage.error(session, Messages.INVALID_CODE);
        }*/
        return null;
    }

    private String getActionCookie() {
        return getActionCookie(headers, realm, uriInfo, clientConnection);
    }

    public static String getActionCookie(HttpHeaders headers, RealmModel realm, UriInfo uriInfo, ClientConnection clientConnection) {
        Cookie cookie = headers.getCookies().get(ACTION_COOKIE);
        AuthenticationManager.expireCookie(realm, ACTION_COOKIE, AuthenticationManager.getRealmCookiePath(realm, uriInfo), realm.getSslRequired().isRequired(clientConnection), clientConnection);
        return cookie != null ? cookie.getValue() : null;
    }

    // TODO: Remove this method. We will be able to use login-session-cookie
    public static void createActionCookie(RealmModel realm, UriInfo uriInfo, ClientConnection clientConnection, String sessionId) {
        CookieHelper.addCookie(ACTION_COOKIE, sessionId, AuthenticationManager.getRealmCookiePath(realm, uriInfo), null, null, -1, realm.getSslRequired().isRequired(clientConnection), true);
    }

    private void initEvent(ClientSessionModel clientSession) {
        UserSessionModel userSession = clientSession.getUserSession();

        String responseType = clientSession.getNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
        if (responseType == null) {
            responseType = "code";
        }
        String respMode = clientSession.getNote(OIDCLoginProtocol.RESPONSE_MODE_PARAM);
        OIDCResponseMode responseMode = OIDCResponseMode.parse(respMode, OIDCResponseType.parse(responseType));

        event.event(EventType.LOGIN).client(clientSession.getClient())
                .user(userSession.getUser())
                .session(userSession.getId())
                .detail(Details.CODE_ID, clientSession.getId())
                .detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
                .detail(Details.USERNAME, clientSession.getNote(AbstractUsernameFormAuthenticator.ATTEMPTED_USERNAME))
                .detail(Details.AUTH_METHOD, userSession.getAuthMethod())
                .detail(Details.USERNAME, userSession.getLoginUsername())
                .detail(Details.RESPONSE_TYPE, responseType)
                .detail(Details.RESPONSE_MODE, responseMode.toString().toLowerCase())
                .detail(Details.IDENTITY_PROVIDER, userSession.getNote(Details.IDENTITY_PROVIDER))
                .detail(Details.IDENTITY_PROVIDER_USERNAME, userSession.getNote(Details.IDENTITY_PROVIDER_USERNAME));

        if (userSession.isRememberMe()) {
            event.detail(Details.REMEMBER_ME, "true");
        }
    }

    private void initLoginEvent(LoginSessionModel loginSession) {
        String responseType = loginSession.getNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
        if (responseType == null) {
            responseType = "code";
        }
        String respMode = loginSession.getNote(OIDCLoginProtocol.RESPONSE_MODE_PARAM);
        OIDCResponseMode responseMode = OIDCResponseMode.parse(respMode, OIDCResponseType.parse(responseType));

        event.event(EventType.LOGIN).client(loginSession.getClient())
                .detail(Details.CODE_ID, loginSession.getId())
                .detail(Details.REDIRECT_URI, loginSession.getRedirectUri())
                .detail(Details.AUTH_METHOD, loginSession.getProtocol())
                .detail(Details.RESPONSE_TYPE, responseType)
                .detail(Details.RESPONSE_MODE, responseMode.toString().toLowerCase());

        UserModel authenticatedUser = loginSession.getAuthenticatedUser();
        if (authenticatedUser != null) {
            event.user(authenticatedUser)
                    .detail(Details.USERNAME, authenticatedUser.getUsername());
        } else {
            event.detail(Details.USERNAME, loginSession.getNote(AbstractUsernameFormAuthenticator.ATTEMPTED_USERNAME));
        }

        // TODO:mposolda Fix if this is called at firstBroker or postBroker login
        /*
                .detail(Details.IDENTITY_PROVIDER, userSession.getNote(Details.IDENTITY_PROVIDER))
                .detail(Details.IDENTITY_PROVIDER_USERNAME, userSession.getNote(Details.IDENTITY_PROVIDER_USERNAME));
                */
    }

    @Path(REQUIRED_ACTION)
    @POST
    public Response requiredActionPOST(@QueryParam("code") final String code,
                                       @QueryParam("action") String action) {
        return processRequireAction(code, action);



    }

    @Path(REQUIRED_ACTION)
    @GET
    public Response requiredActionGET(@QueryParam("code") final String code,
                                       @QueryParam("action") String action) {
        return processRequireAction(code, action);
    }

    private Response processRequireAction(final String code, String action) {
        // TODO:mposolda
        /*
        event.event(EventType.CUSTOM_REQUIRED_ACTION);
        event.detail(Details.CUSTOM_REQUIRED_ACTION, action);
        SessionCodeChecks checks = checksForCode(code);
        if (!checks.verifyRequiredAction(action)) {
            return checks.response;
        }
        final ClientSessionModel clientSession = checks.getClientSession();

        final UserSessionModel userSession = clientSession.getUserSession();

        RequiredActionFactory factory = (RequiredActionFactory)session.getKeycloakSessionFactory().getProviderFactory(RequiredActionProvider.class, action);
        if (factory == null) {
            ServicesLogger.LOGGER.actionProviderNull();
            event.error(Errors.INVALID_CODE);
            throw new WebApplicationException(ErrorPage.error(session, Messages.INVALID_CODE));
        }
        RequiredActionProvider provider = factory.create(session);

        initEvent(clientSession);
        event.event(EventType.CUSTOM_REQUIRED_ACTION);


        RequiredActionContextResult context = new RequiredActionContextResult(userSession, clientSession, realm, event, session, request, userSession.getUser(), factory) {
            @Override
            public void ignore() {
                throw new RuntimeException("Cannot call ignore within processAction()");
            }
        };
        provider.processAction(context);
        if (context.getStatus() == RequiredActionContext.Status.SUCCESS) {
            event.clone().success();
            initEvent(clientSession);
            event.event(EventType.LOGIN);
            clientSession.removeRequiredAction(factory.getId());
            userSession.getUser().removeRequiredAction(factory.getId());
            clientSession.removeNote(AuthenticationManager.CURRENT_REQUIRED_ACTION);

            if (AuthenticationManager.isActionRequired(session, userSession, clientSession, clientConnection, request, uriInfo, event)) {
                // redirect to a generic code URI so that browser refresh will work
                return redirectToRequiredActions(checks.clientCode.getCode());
            } else {
                return AuthenticationManager.finishedRequiredActions(session, userSession, clientSession, clientConnection, request, uriInfo, event);

            }
        }
        if (context.getStatus() == RequiredActionContext.Status.CHALLENGE) {
            return context.getChallenge();
        }
        if (context.getStatus() == RequiredActionContext.Status.FAILURE) {
            LoginProtocol protocol = context.getSession().getProvider(LoginProtocol.class, context.getClientSession().getAuthMethod());
            protocol.setRealm(context.getRealm())
                    .setHttpHeaders(context.getHttpRequest().getHttpHeaders())
                    .setUriInfo(context.getUriInfo())
                    .setEventBuilder(event);

            event.detail(Details.CUSTOM_REQUIRED_ACTION, action);
            Response response = protocol.sendError(context.getClientSession(), Error.CONSENT_DENIED);
            event.error(Errors.REJECTED_BY_USER);
            return response;

        }

        throw new RuntimeException("Unreachable");
        */
        return null;
    }

    private Response redirectToRequiredActions(String code) {
        URI redirect = LoginActionsService.loginActionsBaseUrl(uriInfo)
                .path(LoginActionsService.REQUIRED_ACTION)
                .queryParam(OAuth2Constants.CODE, code).build(realm.getName());
        return Response.status(302).location(redirect).build();
    }

}