/* * 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.authentication.authenticators.resetcred; import org.keycloak.authentication.actiontoken.DefaultActionTokenKey; import org.jboss.logging.Logger; import org.keycloak.Config; import org.keycloak.authentication.*; import org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator; import org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator; import org.keycloak.events.Details; import org.keycloak.events.Errors; import org.keycloak.events.EventBuilder; import org.keycloak.models.AuthenticationExecutionModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.services.messages.Messages; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import java.util.List; /** * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @version $Revision: 1 $ */ public class ResetCredentialChooseUser implements Authenticator, AuthenticatorFactory { private static final Logger logger = Logger.getLogger(ResetCredentialChooseUser.class); public static final String PROVIDER_ID = "reset-credentials-choose-user"; @Override public void authenticate(AuthenticationFlowContext context) { String existingUserId = context.getAuthenticationSession().getAuthNote(AbstractIdpAuthenticator.EXISTING_USER_INFO); if (existingUserId != null) { UserModel existingUser = AbstractIdpAuthenticator.getExistingUser(context.getSession(), context.getRealm(), context.getAuthenticationSession()); logger.debugf("Forget-password triggered when reauthenticating user after first broker login. Skipping reset-credential-choose-user screen and using user '%s' ", existingUser.getUsername()); context.setUser(existingUser); context.success(); return; } String actionTokenUserId = context.getAuthenticationSession().getAuthNote(DefaultActionTokenKey.ACTION_TOKEN_USER_ID); if (actionTokenUserId != null) { UserModel existingUser = context.getSession().users().getUserById(actionTokenUserId, context.getRealm()); // Action token logics handles checks for user ID validity and user being enabled logger.debugf("Forget-password triggered when reauthenticating user after authentication via action token. Skipping reset-credential-choose-user screen and using user '%s' ", existingUser.getUsername()); context.setUser(existingUser); context.success(); return; } Response challenge = context.form().createPasswordReset(); context.challenge(challenge); } @Override public void action(AuthenticationFlowContext context) { EventBuilder event = context.getEvent(); MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters(); String username = formData.getFirst("username"); if (username == null || username.isEmpty()) { event.error(Errors.USERNAME_MISSING); Response challenge = context.form() .setError(Messages.MISSING_USERNAME) .createPasswordReset(); context.failureChallenge(AuthenticationFlowError.INVALID_USER, challenge); return; } username = username.trim(); RealmModel realm = context.getRealm(); UserModel user = context.getSession().users().getUserByUsername(username, realm); if (user == null && realm.isLoginWithEmailAllowed() && username.contains("@")) { user = context.getSession().users().getUserByEmail(username, realm); } context.getAuthenticationSession().setAuthNote(AbstractUsernameFormAuthenticator.ATTEMPTED_USERNAME, username); // we don't want people guessing usernames, so if there is a problem, just continue, but don't set the user // a null user will notify further executions, that this was a failure. if (user == null) { event.clone() .detail(Details.USERNAME, username) .error(Errors.USER_NOT_FOUND); } else if (!user.isEnabled()) { event.clone() .detail(Details.USERNAME, username) .user(user).error(Errors.USER_DISABLED); } else { context.setUser(user); } context.success(); } @Override public boolean requiresUser() { return false; } @Override public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) { return true; } @Override public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) { } @Override public String getDisplayType() { return "Choose User"; } @Override public String getReferenceCategory() { return null; } @Override public boolean isConfigurable() { return false; } public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = { AuthenticationExecutionModel.Requirement.REQUIRED }; @Override public AuthenticationExecutionModel.Requirement[] getRequirementChoices() { return REQUIREMENT_CHOICES; } @Override public boolean isUserSetupAllowed() { return false; } @Override public String getHelpText() { return "Choose a user to reset credentials for"; } @Override public List<ProviderConfigProperty> getConfigProperties() { return null; } @Override public void close() { } @Override public Authenticator create(KeycloakSession session) { return this; } @Override public void init(Config.Scope config) { } @Override public void postInit(KeycloakSessionFactory factory) { } @Override public String getId() { return PROVIDER_ID; } }