/* * Copyright 2017 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.authentication.actiontoken.execactions; import org.keycloak.TokenVerifier.Predicate; import org.keycloak.authentication.RequiredActionFactory; import org.keycloak.authentication.RequiredActionProvider; import org.keycloak.authentication.actiontoken.*; import org.keycloak.events.Errors; import org.keycloak.events.EventType; import org.keycloak.models.*; import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.utils.RedirectUtils; import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.messages.Messages; import org.keycloak.sessions.AuthenticationSessionModel; import java.util.Objects; import javax.ws.rs.core.Response; /** * * @author hmlnarik */ public class ExecuteActionsActionTokenHandler extends AbstractActionTokenHander<ExecuteActionsActionToken> { public ExecuteActionsActionTokenHandler() { super( ExecuteActionsActionToken.TOKEN_TYPE, ExecuteActionsActionToken.class, Messages.INVALID_CODE, EventType.EXECUTE_ACTIONS, Errors.NOT_ALLOWED ); } @Override public Predicate<? super ExecuteActionsActionToken>[] getVerifiers(ActionTokenContext<ExecuteActionsActionToken> tokenContext) { return TokenUtils.predicates( TokenUtils.checkThat( // either redirect URI is not specified or must be valid for the client t -> t.getRedirectUri() == null || RedirectUtils.verifyRedirectUri(tokenContext.getUriInfo(), t.getRedirectUri(), tokenContext.getRealm(), tokenContext.getAuthenticationSession().getClient()) != null, Errors.INVALID_REDIRECT_URI, Messages.INVALID_REDIRECT_URI ) ); } @Override public Response handleToken(ExecuteActionsActionToken token, ActionTokenContext<ExecuteActionsActionToken> tokenContext) { AuthenticationSessionModel authSession = tokenContext.getAuthenticationSession(); String redirectUri = RedirectUtils.verifyRedirectUri(tokenContext.getUriInfo(), token.getRedirectUri(), tokenContext.getRealm(), authSession.getClient()); if (redirectUri != null) { authSession.setAuthNote(AuthenticationManager.SET_REDIRECT_URI_AFTER_REQUIRED_ACTIONS, "true"); authSession.setRedirectUri(redirectUri); authSession.setClientNote(OIDCLoginProtocol.REDIRECT_URI_PARAM, redirectUri); } token.getRequiredActions().stream().forEach(authSession::addRequiredAction); UserModel user = tokenContext.getAuthenticationSession().getAuthenticatedUser(); // verify user email as we know it is valid as this entry point would never have gotten here. user.setEmailVerified(true); String nextAction = AuthenticationManager.nextRequiredAction(tokenContext.getSession(), authSession, tokenContext.getClientConnection(), tokenContext.getRequest(), tokenContext.getUriInfo(), tokenContext.getEvent()); return AuthenticationManager.redirectToRequiredActions(tokenContext.getSession(), tokenContext.getRealm(), authSession, tokenContext.getUriInfo(), nextAction); } @Override public boolean canUseTokenRepeatedly(ExecuteActionsActionToken token, ActionTokenContext<ExecuteActionsActionToken> tokenContext) { RealmModel realm = tokenContext.getRealm(); KeycloakSessionFactory sessionFactory = tokenContext.getSession().getKeycloakSessionFactory(); return token.getRequiredActions().stream() .map(actionName -> realm.getRequiredActionProviderByAlias(actionName)) // get realm-specific model from action name and filter out irrelevant .filter(Objects::nonNull) .filter(RequiredActionProviderModel::isEnabled) .map(RequiredActionProviderModel::getProviderId) // get provider ID from model .map(providerId -> (RequiredActionFactory) sessionFactory.getProviderFactory(RequiredActionProvider.class, providerId)) .filter(Objects::nonNull) .noneMatch(RequiredActionFactory::isOneTimeAction); } }