/* * 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.broker; import org.jboss.logging.Logger; import org.keycloak.authentication.AuthenticationFlowContext; import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext; import org.keycloak.broker.provider.BrokeredIdentityContext; import org.keycloak.common.util.ObjectUtil; import org.keycloak.events.Details; import org.keycloak.events.EventBuilder; import org.keycloak.events.EventType; import org.keycloak.forms.login.LoginFormsProvider; import org.keycloak.models.AuthenticatorConfigModel; import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; import org.keycloak.models.utils.FormMessage; import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.services.resources.AttributeFormDataProcessor; import org.keycloak.services.validation.Validation; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import java.util.List; /** * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> */ public class IdpReviewProfileAuthenticator extends AbstractIdpAuthenticator { private static final Logger logger = Logger.getLogger(IdpReviewProfileAuthenticator.class); @Override public boolean requiresUser() { return false; } @Override protected void authenticateImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext userCtx, BrokeredIdentityContext brokerContext) { IdentityProviderModel idpConfig = brokerContext.getIdpConfig(); if (requiresUpdateProfilePage(context, userCtx, brokerContext)) { logger.debugf("Identity provider '%s' requires update profile action for broker user '%s'.", idpConfig.getAlias(), userCtx.getUsername()); // No formData for first render. The profile is rendered from userCtx Response challengeResponse = context.form() .setAttribute(LoginFormsProvider.UPDATE_PROFILE_CONTEXT_ATTR, userCtx) .setFormData(null) .createUpdateProfilePage(); context.challenge(challengeResponse); } else { // Not required to update profile. Marked success context.success(); } } protected boolean requiresUpdateProfilePage(AuthenticationFlowContext context, SerializedBrokeredIdentityContext userCtx, BrokeredIdentityContext brokerContext) { String enforceUpdateProfile = context.getAuthenticationSession().getAuthNote(ENFORCE_UPDATE_PROFILE); if (Boolean.parseBoolean(enforceUpdateProfile)) { return true; } String updateProfileFirstLogin; AuthenticatorConfigModel authenticatorConfig = context.getAuthenticatorConfig(); if (authenticatorConfig == null || !authenticatorConfig.getConfig().containsKey(IdpReviewProfileAuthenticatorFactory.UPDATE_PROFILE_ON_FIRST_LOGIN)) { updateProfileFirstLogin = IdentityProviderRepresentation.UPFLM_MISSING; } else { updateProfileFirstLogin = authenticatorConfig.getConfig().get(IdpReviewProfileAuthenticatorFactory.UPDATE_PROFILE_ON_FIRST_LOGIN); } RealmModel realm = context.getRealm(); return IdentityProviderRepresentation.UPFLM_ON.equals(updateProfileFirstLogin) || (IdentityProviderRepresentation.UPFLM_MISSING.equals(updateProfileFirstLogin) && !Validation.validateUserMandatoryFields(realm, userCtx)); } @Override protected void actionImpl(AuthenticationFlowContext context, SerializedBrokeredIdentityContext userCtx, BrokeredIdentityContext brokerContext) { EventBuilder event = context.getEvent(); event.event(EventType.UPDATE_PROFILE); MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters(); RealmModel realm = context.getRealm(); List<FormMessage> errors = Validation.validateUpdateProfileForm(!realm.isRegistrationEmailAsUsername(), formData); if (errors != null && !errors.isEmpty()) { Response challenge = context.form() .setErrors(errors) .setAttribute(LoginFormsProvider.UPDATE_PROFILE_CONTEXT_ATTR, userCtx) .setFormData(formData) .createUpdateProfilePage(); context.challenge(challenge); return; } String username = realm.isRegistrationEmailAsUsername() ? formData.getFirst(UserModel.EMAIL) : formData.getFirst(UserModel.USERNAME); userCtx.setUsername(username); userCtx.setFirstName(formData.getFirst(UserModel.FIRST_NAME)); userCtx.setLastName(formData.getFirst(UserModel.LAST_NAME)); String email = formData.getFirst(UserModel.EMAIL); if (!ObjectUtil.isEqualOrBothNull(email, userCtx.getEmail())) { if (logger.isTraceEnabled()) { logger.tracef("Email updated on updateProfile page to '%s' ", email); } userCtx.setEmail(email); context.getAuthenticationSession().setAuthNote(UPDATE_PROFILE_EMAIL_CHANGED, "true"); } AttributeFormDataProcessor.process(formData, realm, userCtx); userCtx.saveToAuthenticationSession(context.getAuthenticationSession(), BROKERED_CONTEXT_NOTE); logger.debugf("Profile updated successfully after first authentication with identity provider '%s' for broker user '%s'.", brokerContext.getIdpConfig().getAlias(), userCtx.getUsername()); event.detail(Details.UPDATED_EMAIL, email); context.success(); } @Override public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) { return true; } }