/* * Copyright (c) 2012-2017 Biznet, Evolveum * * 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 com.evolveum.midpoint.web.page.forgetpassword; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import org.apache.wicket.RestartResponseException; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.extensions.validation.validator.RfcCompliantEmailAddressValidator; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.basic.MultiLineLabel; import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.markup.html.form.RequiredTextField; import org.apache.wicket.model.Model; import org.apache.wicket.request.mapper.parameter.PageParameters; import com.evolveum.midpoint.gui.api.page.PageBase; import com.evolveum.midpoint.gui.api.util.WebModelServiceUtils; import com.evolveum.midpoint.model.common.stringpolicy.ValuePolicyProcessor; import com.evolveum.midpoint.prism.Item; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.PrismProperty; import com.evolveum.midpoint.prism.PrismPropertyDefinition; import com.evolveum.midpoint.prism.PrismReference; import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.prism.match.DefaultMatchingRule; import com.evolveum.midpoint.prism.match.MatchingRule; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.query.AndFilter; import com.evolveum.midpoint.prism.query.EqualFilter; import com.evolveum.midpoint.prism.query.ObjectFilter; import com.evolveum.midpoint.prism.query.ObjectQuery; import com.evolveum.midpoint.prism.query.builder.QueryBuilder; import com.evolveum.midpoint.prism.query.builder.R_AtomicFilter; import com.evolveum.midpoint.prism.query.builder.R_Filter; import com.evolveum.midpoint.prism.query.builder.S_FilterEntryOrEmpty; import com.evolveum.midpoint.schema.GetOperationOptions; import com.evolveum.midpoint.schema.SearchResultList; import com.evolveum.midpoint.schema.SelectorOptions; import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.result.OperationResultStatus; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.Producer; import com.evolveum.midpoint.util.exception.CommunicationException; import com.evolveum.midpoint.util.exception.ConfigurationException; import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; import com.evolveum.midpoint.util.exception.ObjectNotFoundException; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.exception.SecurityViolationException; import com.evolveum.midpoint.util.logging.LoggingUtils; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.web.application.PageDescriptor; import com.evolveum.midpoint.web.component.AjaxButton; import com.evolveum.midpoint.web.component.AjaxSubmitButton; import com.evolveum.midpoint.web.component.prism.DynamicFormPanel; import com.evolveum.midpoint.web.component.util.VisibleEnableBehaviour; import com.evolveum.midpoint.web.page.forgetpassword.ResetPolicyDto.ResetMethod; import com.evolveum.midpoint.web.page.login.PageLogin; import com.evolveum.midpoint.web.page.login.PageRegistrationBase; import com.evolveum.midpoint.web.util.OnePageParameterEncoder; import com.evolveum.midpoint.xml.ns._public.common.common_3.NonceCredentialsPolicyType; import com.evolveum.midpoint.xml.ns._public.common.common_3.NonceType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ValuePolicyType; import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType; @PageDescriptor(url = "/forgotpassword") public class PageForgotPassword extends PageRegistrationBase { private static final long serialVersionUID = 1L; private static final String ID_PWDRESETFORM = "pwdresetform"; private static final String ID_USERNAME_CONTAINER = "usernameContainer"; private static final String ID_USERNAME = "username"; private static final String ID_EMAIL_CONTAINER = "emailContainer"; private static final String ID_EMAIL = "email"; private static final String ID_SUBMIT = "submitButton"; private static final String ID_BACK = "back"; private static final String ID_STATIC_LAYOUT = "staticLayout"; private static final String ID_DYNAMIC_LAYOUT = "dynamicLayout"; private static final String ID_DYNAMIC_FORM = "dynamicForm"; private static final String DOT_CLASS = PageForgotPassword.class.getName() + "."; protected static final String OPERATION_LOAD_RESET_PASSWORD_POLICY = DOT_CLASS + "loadPasswordResetPolicy"; private static final String ID_PASSWORD_RESET_SUBMITED = "resetPasswordInfo"; private static final String OPERATION_LOAD_USER = DOT_CLASS + "loadUser"; private static final Trace LOGGER = TraceManager.getTrace(PageForgotPassword.class); public PageForgotPassword() { super(); initLayout(); } private boolean submited; @Override protected void createBreadcrumb() { // don't create breadcrumb for this page } private void initLayout() { Form<?> form = new Form(ID_PWDRESETFORM); form.setOutputMarkupId(true); form.add(new VisibleEnableBehaviour() { private static final long serialVersionUID = 1L; @Override public boolean isVisible() { return !submited; } }); initStaticLayout(form); initDynamicLayout(form); initButtons(form); } private void initStaticLayout(Form<?> mainForm) { WebMarkupContainer staticLayout = new WebMarkupContainer(ID_STATIC_LAYOUT); staticLayout.setOutputMarkupId(true); mainForm.add(staticLayout); staticLayout.add(new VisibleEnableBehaviour() { private static final long serialVersionUID = 1L; @Override public boolean isVisible() { return !isDynamicForm(); } }); WebMarkupContainer userNameContainer = new WebMarkupContainer(ID_USERNAME_CONTAINER); userNameContainer.setOutputMarkupId(true); staticLayout.add(userNameContainer); RequiredTextField<String> userName = new RequiredTextField<String>(ID_USERNAME, new Model<String>()); userName.setOutputMarkupId(true); userNameContainer.add(userName); userNameContainer.add(new VisibleEnableBehaviour() { private static final long serialVersionUID = 1L; public boolean isVisible() { return getResetPasswordPolicy().getResetMethod() == ResetMethod.SECURITY_QUESTIONS; }; }); WebMarkupContainer emailContainer = new WebMarkupContainer(ID_EMAIL_CONTAINER); emailContainer.setOutputMarkupId(true); staticLayout.add(emailContainer); RequiredTextField<String> email = new RequiredTextField<String>(ID_EMAIL, new Model<String>()); email.add(RfcCompliantEmailAddressValidator.getInstance()); email.setOutputMarkupId(true); emailContainer.add(email); emailContainer.add(new VisibleEnableBehaviour() { private static final long serialVersionUID = 1L; public boolean isVisible() { ResetMethod resetMethod = getResetPasswordPolicy().getResetMethod(); return resetMethod == ResetMethod.SECURITY_QUESTIONS || resetMethod == ResetMethod.MAIL; }; }); } private boolean isDynamicForm() { return getResetPasswordPolicy().getFormRef() != null; } private void initDynamicLayout(final Form<?> mainForm) { WebMarkupContainer dynamicLayout = new WebMarkupContainer(ID_DYNAMIC_LAYOUT); dynamicLayout.setOutputMarkupId(true); mainForm.add(dynamicLayout); dynamicLayout.add(new VisibleEnableBehaviour() { private static final long serialVersionUID = 1L; @Override public boolean isVisible() { return isDynamicForm(); } }); DynamicFormPanel<UserType> searchAttributesForm = runPrivileged( () -> { ObjectReferenceType formRef = getResetPasswordPolicy().getFormRef(); if (formRef == null) { return null; } return new DynamicFormPanel<UserType>(ID_DYNAMIC_FORM, UserType.COMPLEX_TYPE, formRef.getOid(), mainForm, true, this); }); if (searchAttributesForm != null) { dynamicLayout.add(searchAttributesForm); } } private void initButtons(Form<?> form) { AjaxSubmitButton submit = new AjaxSubmitButton(ID_SUBMIT) { private static final long serialVersionUID = 1L; @Override protected void onSubmit(AjaxRequestTarget target, Form<?> form) { processResetPassword(target, form); } @Override protected void onError(AjaxRequestTarget target, Form<?> form) { target.add(getFeedbackPanel()); } }; submit.setOutputMarkupId(true); form.add(submit); AjaxButton backButton = new AjaxButton(ID_BACK) { private static final long serialVersionUID = 1L; @Override public void onClick(AjaxRequestTarget target) { setResponsePage(PageLogin.class); } }; backButton.setOutputMarkupId(true); form.add(backButton); add(form); MultiLineLabel label = new MultiLineLabel(ID_PASSWORD_RESET_SUBMITED, createStringResource("PageForgotPassword.form.submited.message")); add(label); label.add(new VisibleEnableBehaviour() { private static final long serialVersionUID = 1L; @Override public boolean isVisible() { return submited; } @Override public boolean isEnabled() { return submited; } }); } private void processResetPassword(AjaxRequestTarget target, Form<?> form) { UserType user = searchUser(form); if (user == null) { getSession().error(getString("pageForgetPassword.message.user.not.found")); throw new RestartResponseException(PageForgotPassword.class); } LOGGER.trace("Reset Password user: {}", user); if (getResetPasswordPolicy() == null) { LOGGER.debug("No policies for reset password defined"); getSession().error(getString("pageForgetPassword.message.policy.not.found")); throw new RestartResponseException(PageForgotPassword.class); } switch (getResetPasswordPolicy().getResetMethod()) { case MAIL: OperationResult result = saveUserNonce(user, getResetPasswordPolicy().getNoncePolicy()); if (result.getStatus() == OperationResultStatus.SUCCESS) { submited = true; target.add(PageForgotPassword.this); } else { getSession().error(getString("PageForgotPassword.send.nonce.failed")); LOGGER.error("Failed to sent none to user: {} ", result.getMessage()); throw new RestartResponseException(PageForgotPassword.this); } break; case SECURITY_QUESTIONS: LOGGER.trace("Forward to PageSecurityQuestions"); PageParameters params = new PageParameters(); params.add(PageSecurityQuestions.SESSION_ATTRIBUTE_POID, user.getOid()); setResponsePage(PageSecurityQuestions.class, params); break; default: getSession().error(getString("pageForgetPassword.message.reset.method.not.supported")); LOGGER.error("Reset method {} not supported.", getResetPasswordPolicy().getResetMethod()); throw new RestartResponseException(PageForgotPassword.this); } } private UserType searchUser(Form form) { ObjectQuery query = null; if (isDynamicForm()) { query = createDynamicFormQuery(form); } else { query = createStaticFormQuery(form); } if (LOGGER.isTraceEnabled()) { LOGGER.trace("Searching for user with query:\n{}", query.debugDump(1)); } return searchUserPrivileged(query); } private ObjectQuery createDynamicFormQuery(Form form) { DynamicFormPanel<UserType> userDynamicPanel = (DynamicFormPanel<UserType>) form .get(createComponentPath(ID_DYNAMIC_LAYOUT, ID_DYNAMIC_FORM)); List<ItemPath> filledItems = userDynamicPanel.getChangedItems(); PrismObject<UserType> user; try { user = userDynamicPanel.getObject(); } catch (SchemaException e1) { getSession().error(getString("pageForgetPassword.message.usernotfound")); throw new RestartResponseException(PageForgotPassword.class); } List<EqualFilter> filters = new ArrayList<>(); for (ItemPath path : filledItems) { PrismProperty property = user.findProperty(path); EqualFilter filter = EqualFilter.createEqual(path, property.getDefinition(), null); filter.setValue(property.getAnyValue().clone()); filters.add(filter); } return ObjectQuery.createObjectQuery(AndFilter.createAnd((List) filters)); } private ObjectQuery createStaticFormQuery(Form form) { RequiredTextField<String> usernameTextFiled = (RequiredTextField) form .get(createComponentPath(ID_STATIC_LAYOUT, ID_USERNAME_CONTAINER, ID_USERNAME)); RequiredTextField<String> emailTextField = (RequiredTextField) form .get(createComponentPath(ID_STATIC_LAYOUT, ID_EMAIL_CONTAINER, ID_EMAIL)); String username = usernameTextFiled != null ? usernameTextFiled.getModelObject() : null; String email = emailTextField != null ? emailTextField.getModelObject() : null; LOGGER.debug("Reset Password user info form submitted. username={}, email={}", username, email); switch (getResetPasswordPolicy().getResetMethod()) { case MAIL: return QueryBuilder.queryFor(UserType.class, getPrismContext()).item(UserType.F_EMAIL_ADDRESS) .eq(email).matchingCaseIgnore().build(); case SECURITY_QUESTIONS: return QueryBuilder.queryFor(UserType.class, getPrismContext()).item(UserType.F_NAME) .eqPoly(username).matchingNorm().and().item(UserType.F_EMAIL_ADDRESS).eq(email) .matchingCaseIgnore().build(); default: getSession().error(getString("PageForgotPassword.unsupported.reset.type")); throw new RestartResponseException(PageForgotPassword.this); } } private UserType searchUserPrivileged(ObjectQuery query) { UserType userType = runPrivileged(new Producer<UserType>() { @Override public UserType run() { Task task = createAnonymousTask("load user"); OperationResult result = new OperationResult("search user"); SearchResultList<PrismObject<UserType>> users; try { users = getModelService().searchObjects(UserType.class, query, null, task, result); } catch (SchemaException | ObjectNotFoundException | SecurityViolationException | CommunicationException | ConfigurationException e) { LoggingUtils.logException(LOGGER, "failed to search user", e); return null; } if ((users == null) || (users.isEmpty())) { LOGGER.trace("Empty user list in ForgetPassword"); return null; } if (users.size() > 1) { LOGGER.trace("Problem while seeking for user"); return null; } UserType user = users.iterator().next().asObjectable(); LOGGER.trace("User found for ForgetPassword: {}", user); return user; } }); return userType; } private OperationResult saveUserNonce(final UserType user, final NonceCredentialsPolicyType noncePolicy) { return runPrivileged(new Producer<OperationResult>() { @Override public OperationResult run() { Task task = createAnonymousTask("generateUserNonce"); task.setChannel(SchemaConstants.CHANNEL_GUI_RESET_PASSWORD_URI); task.setOwner(user.asPrismObject()); OperationResult result = new OperationResult("generateUserNonce"); ProtectedStringType nonceCredentials = new ProtectedStringType(); try { nonceCredentials .setClearValue(generateNonce(noncePolicy, task, user.asPrismObject(), result)); NonceType nonceType = new NonceType(); nonceType.setValue(nonceCredentials); ObjectDelta<UserType> nonceDelta; nonceDelta = ObjectDelta.createModificationReplaceContainer(UserType.class, user.getOid(), SchemaConstants.PATH_NONCE, getPrismContext(), nonceType); WebModelServiceUtils.save(nonceDelta, result, task, PageForgotPassword.this); } catch (SchemaException | ExpressionEvaluationException | ObjectNotFoundException e) { result.recordFatalError("Failed to generate nonce for user"); LoggingUtils.logException(LOGGER, "Failed to generate nonce for user: " + e.getMessage(), e); } result.computeStatusIfUnknown(); return result; } }); } private <O extends ObjectType> String generateNonce(NonceCredentialsPolicyType noncePolicy, Task task, PrismObject<O> user, OperationResult result) throws ExpressionEvaluationException, SchemaException, ObjectNotFoundException { ValuePolicyType policy = null; if (noncePolicy != null && noncePolicy.getValuePolicyRef() != null) { PrismObject<ValuePolicyType> valuePolicy = WebModelServiceUtils.loadObject(ValuePolicyType.class, noncePolicy.getValuePolicyRef().getOid(), PageForgotPassword.this, task, result); policy = valuePolicy.asObjectable(); } return getModelInteractionService().generateValue(policy != null ? policy.getStringPolicy() : null, 24, false, user, "nonce generation", task, result); } }