/* * Copyright (c) 2010-2017 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.login; import org.apache.commons.lang.StringUtils; import org.apache.wicket.AttributeModifier; import org.apache.wicket.RestartResponseException; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.feedback.ContainerFeedbackMessageFilter; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.basic.MultiLineLabel; import org.apache.wicket.markup.html.form.FormComponent; import org.apache.wicket.markup.html.panel.FeedbackPanel; import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; import org.apache.wicket.model.PropertyModel; import org.apache.wicket.model.StringResourceModel; import org.apache.wicket.request.mapper.parameter.PageParameters; import org.apache.wicket.util.string.StringValue; import com.evolveum.midpoint.gui.api.component.captcha.CaptchaPanel; import com.evolveum.midpoint.gui.api.component.password.PasswordPanel; import com.evolveum.midpoint.gui.api.model.LoadableModel; import com.evolveum.midpoint.gui.api.util.WebModelServiceUtils; import com.evolveum.midpoint.model.api.ModelExecuteOptions; import com.evolveum.midpoint.model.common.stringpolicy.ValuePolicyProcessor; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.PrismObjectDefinition; import com.evolveum.midpoint.prism.delta.ObjectDelta; 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.ExpressionEvaluationException; import com.evolveum.midpoint.util.exception.ObjectNotFoundException; import com.evolveum.midpoint.util.exception.SchemaException; 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.application.Url; import com.evolveum.midpoint.web.component.AjaxButton; import com.evolveum.midpoint.web.component.AjaxSubmitButton; import com.evolveum.midpoint.web.component.form.Form; import com.evolveum.midpoint.web.component.input.TextPanel; import com.evolveum.midpoint.web.component.prism.DynamicFormPanel; import com.evolveum.midpoint.web.component.util.VisibleEnableBehaviour; import com.evolveum.midpoint.web.page.admin.configuration.component.EmptyOnBlurAjaxFormUpdatingBehaviour; import com.evolveum.midpoint.web.util.InfoTooltipBehavior; import com.evolveum.midpoint.xml.ns._public.common.common_3.CredentialsType; 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.PasswordType; 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.PolyStringType; import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType; //"http://localhost:8080/midpoint/confirm/registrationid=" + newUser.getOid() //+ "/token=" + userType.getCostCenter() + "/roleId=00000000-0000-0000-0000-000000000008"; @PageDescriptor(urls = {@Url(mountUrl = "/registration")}) public class PageSelfRegistration extends PageRegistrationBase { private static final Trace LOGGER = TraceManager.getTrace(PageSelfRegistration.class); private static final String DOT_CLASS = PageSelfRegistration.class.getName() + "."; private static final String ID_MAIN_FORM = "mainForm"; private static final String ID_FIRST_NAME = "firstName"; private static final String ID_LAST_NAME = "lastName"; private static final String ID_EMAIL = "email"; private static final String ID_PASSWORD = "password"; private static final String ID_SUBMIT_REGISTRATION = "submitRegistration"; private static final String ID_REGISTRATION_SUBMITED = "registrationInfo"; private static final String ID_FEEDBACK = "feedback"; private static final String ID_BACK = "back"; private static final String ID_WELCOME = "welcome"; private static final String ID_ADDITIONAL_TEXT = "additionalText"; private static final String ID_TOOLTIP = "tooltip"; private static final String ID_DYNAMIC_FORM_PANEL = "registrationForm"; private static final String ID_STATIC_FORM = "staticForm"; private static final String ID_DYNAMIC_FORM = "dynamicForm"; private static final String ID_CAPTCHA = "captcha"; private static final String OPERATION_SAVE_USER = DOT_CLASS + "saveUser"; private static final String OPERATION_LOAD_USER = DOT_CLASS + "loadUser"; private static final String PARAM_USER_OID = "user"; private static final long serialVersionUID = 1L; private IModel<UserType> userModel; private boolean submited = false; // String randomString = null; // String captchaString = null; public PageSelfRegistration() { this(null); } public PageSelfRegistration(PageParameters pageParameters) { super(); final String userOid = getOidFromParams(pageParameters); userModel = new LoadableModel<UserType>(false) { private static final long serialVersionUID = 1L; @Override protected UserType load() { return createUserModel(userOid); } }; initLayout(); } private String getOidFromParams(PageParameters pageParameters) { if (pageParameters == null) { return null; } StringValue oidValue = pageParameters.get(PARAM_USER_OID); if (oidValue != null) { return oidValue.toString(); } return null; } private UserType createUserModel(final String userOid) { if (userOid != null) { PrismObject<UserType> result = runPrivileged(new Producer<PrismObject<UserType>>() { @Override public PrismObject<UserType> run() { LOGGER.trace("Loading preregistered user with oid {}.", userOid); Task task = createAnonymousTask(OPERATION_LOAD_USER); OperationResult result = new OperationResult(OPERATION_LOAD_USER); PrismObject<UserType> user = WebModelServiceUtils.loadObject(UserType.class, userOid, PageSelfRegistration.this, task, result); result.computeStatus(); return user; } }); if (result == null) { LOGGER.error("Failed to load preregistered user"); getSession().error( createStringResource("PageSelfRegistration.invalid.registration.link").getString()); throw new RestartResponseException(PageLogin.class); } return result.asObjectable(); } LOGGER.trace("Registration process for new user started"); return instantiateUser(); } private UserType instantiateUser() { PrismObjectDefinition<UserType> userDef = getUserDefinition(); PrismObject<UserType> user; try { user = userDef.instantiate(); } catch (SchemaException e) { UserType userType = new UserType(); user = userType.asPrismObject(); } return user.asObjectable(); } private PrismObjectDefinition<UserType> getUserDefinition() { return getPrismContext().getSchemaRegistry().findObjectDefinitionByCompileTimeClass(UserType.class); } private void initLayout() { final Form<?> mainForm = new Form<>(ID_MAIN_FORM); initAccessBehaviour(mainForm); add(mainForm); addMultilineLable(ID_WELCOME, "PageSelfRegistration.welcome.message", mainForm); addMultilineLable(ID_ADDITIONAL_TEXT, "PageSelfRegistration.additional.message", mainForm); initStaticFormLayout(mainForm); initDynamicFormLayout(mainForm); CaptchaPanel captcha = new CaptchaPanel(ID_CAPTCHA); captcha.setOutputMarkupId(true); mainForm.add(captcha); AjaxSubmitButton register = new AjaxSubmitButton(ID_SUBMIT_REGISTRATION) { private static final long serialVersionUID = 1L; @Override protected void onError(AjaxRequestTarget target, org.apache.wicket.markup.html.form.Form<?> form) { showErrors(target); } protected void onSubmit(AjaxRequestTarget target, org.apache.wicket.markup.html.form.Form<?> form) { submitRegistration(target); } }; mainForm.add(register); MultiLineLabel label = new MultiLineLabel(ID_REGISTRATION_SUBMITED, createStringResource("PageSelfRegistration.registration.confirm.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; } }); AjaxButton back = new AjaxButton(ID_BACK) { private static final long serialVersionUID = 1L; @Override public void onClick(AjaxRequestTarget target) { setResponsePage(PageLogin.class); } }; mainForm.add(back); } private void initStaticFormLayout(Form<?> mainForm) { // feedback FeedbackPanel feedback = new FeedbackPanel(ID_FEEDBACK, new ContainerFeedbackMessageFilter(PageSelfRegistration.this)); feedback.setOutputMarkupId(true); mainForm.add(feedback); WebMarkupContainer staticRegistrationForm = createMarkupContainer(ID_STATIC_FORM, new VisibleEnableBehaviour() { private static final long serialVersionUID = 1L; @Override public boolean isVisible() { return getSelfRegistrationConfiguration().getFormRef() == null; } }, mainForm); TextPanel<String> firstName = new TextPanel<>(ID_FIRST_NAME, new PropertyModel<String>(userModel, UserType.F_GIVEN_NAME.getLocalPart() + ".orig") { private static final long serialVersionUID = 1L; @Override public void setObject(String object) { userModel.getObject().setGivenName(new PolyStringType(object)); } }); initInputProperties(feedback, firstName); staticRegistrationForm.add(firstName); TextPanel<String> lastName = new TextPanel<>(ID_LAST_NAME, new PropertyModel<String>(userModel, UserType.F_FAMILY_NAME.getLocalPart() + ".orig") { private static final long serialVersionUID = 1L; @Override public void setObject(String object) { userModel.getObject().setFamilyName(new PolyStringType(object)); } }); initInputProperties(feedback, lastName); staticRegistrationForm.add(lastName); TextPanel<String> email = new TextPanel<>(ID_EMAIL, new PropertyModel<String>(userModel, UserType.F_EMAIL_ADDRESS.getLocalPart())); initInputProperties(feedback, email); staticRegistrationForm.add(email); createPasswordPanel(staticRegistrationForm); } private void initDynamicFormLayout(final Form<?> mainForm) { WebMarkupContainer dynamicRegistrationForm = createMarkupContainer(ID_DYNAMIC_FORM, new VisibleEnableBehaviour() { private static final long serialVersionUID = 1L; @Override public boolean isVisible() { return getSelfRegistrationConfiguration().getFormRef() != null; } }, mainForm); DynamicFormPanel<UserType> dynamicForm = runPrivileged(new Producer<DynamicFormPanel<UserType>>() { @Override public DynamicFormPanel<UserType> run() { final ObjectReferenceType ort = getSelfRegistrationConfiguration().getFormRef(); if (ort == null) { return null; } DynamicFormPanel<UserType> dynamicForm = new DynamicFormPanel<UserType>(ID_DYNAMIC_FORM_PANEL, userModel, ort.getOid(), mainForm, true, PageSelfRegistration.this); return dynamicForm; } }); if (dynamicForm != null) { dynamicRegistrationForm.add(dynamicForm); } } private void createPasswordPanel(WebMarkupContainer staticRegistrationForm) { // ProtectedStringType initialPassword = null; PasswordPanel password = new PasswordPanel(ID_PASSWORD, new PropertyModel<ProtectedStringType>(userModel, "credentials.password.value"), false, true, false); password.getBaseFormComponent().add(new EmptyOnBlurAjaxFormUpdatingBehaviour()); password.getBaseFormComponent().setRequired(true); staticRegistrationForm.add(password); Label help = new Label(ID_TOOLTIP); final StringResourceModel tooltipText = createStringResource("PageSelfRegistration.password.policy"); help.add(AttributeModifier.replace("title", tooltipText)); help.add(new InfoTooltipBehavior()); help.add(new VisibleEnableBehaviour() { private static final long serialVersionUID = 1L; @Override public boolean isVisible() { return StringUtils.isNotEmpty(tooltipText.getObject()); } }); staticRegistrationForm.add(help); } private WebMarkupContainer createMarkupContainer(String id, VisibleEnableBehaviour visibleEnableBehaviour, Form<?> mainForm) { WebMarkupContainer formContainer = new WebMarkupContainer(id); formContainer.setOutputMarkupId(true); formContainer.add(visibleEnableBehaviour); mainForm.add(formContainer); return formContainer; } private void addMultilineLable(String id, String messageKey, Form<?> mainForm) { MultiLineLabel welcome = new MultiLineLabel(id, createStringResource(messageKey)); welcome.setOutputMarkupId(true); welcome.add(new VisibleEnableBehaviour() { private static final long serialVersionUID = 1L; @Override public boolean isVisible() { return !submited; } }); mainForm.add(welcome); } private void initAccessBehaviour(Form<?> mainForm) { mainForm.add(new VisibleEnableBehaviour() { private static final long serialVersionUID = 1L; @Override public boolean isVisible() { return !submited; } @Override public boolean isEnabled() { return !submited; } }); } private void showErrors(AjaxRequestTarget target) { target.add(get(createComponentPath(ID_MAIN_FORM, ID_FEEDBACK))); target.add(getFeedbackPanel()); } private void initInputProperties(FeedbackPanel feedback, TextPanel<String> input) { input.getBaseFormComponent().add(new EmptyOnBlurAjaxFormUpdatingBehaviour()); input.getBaseFormComponent().setRequired(true); feedback.setFilter(new ContainerFeedbackMessageFilter(input.getBaseFormComponent())); input.add(new VisibleEnableBehaviour() { private static final long serialVersionUID = 1L; @Override public boolean isEnabled() { return getOidFromParams(getPageParameters()) == null; } }); } private CaptchaPanel getCaptcha() { return (CaptchaPanel) get(createComponentPath(ID_MAIN_FORM, ID_CAPTCHA)); } private void submitRegistration(AjaxRequestTarget target) { if (!validateCaptcha(target)) { return; } OperationResult result = runPrivileged(new Producer<OperationResult>() { @Override public OperationResult run() { Task task = createAnonymousTask(OPERATION_SAVE_USER); task.setChannel(SchemaConstants.CHANNEL_GUI_SELF_REGISTRATION_URI); OperationResult result = new OperationResult(OPERATION_SAVE_USER); saveUser(task, result); result.computeStatus(); return result; } }); if (result.getStatus() == OperationResultStatus.SUCCESS) { submited = true; getSession() .success(createStringResource("PageSelfRegistration.registration.success").getString()); switch (getSelfRegistrationConfiguration().getAuthenticationMethod()) { case MAIL: target.add(PageSelfRegistration.this); break; case SMS: throw new UnsupportedOperationException(); case NONE: setResponsePage(PageLogin.class); } LOGGER.trace("Registration for user {} was successfull.", userModel.getObject()); } else { getSession().error( createStringResource("PageSelfRegistration.registration.error", result.getMessage()) .getString()); // removePassword(target); updateCaptcha(target); target.add(getFeedbackPanel()); LOGGER.error("Failed to register user {}. Reason {}", userModel.getObject(), result.getMessage()); return; } target.add(getFeedbackPanel()); target.add(this); } private boolean validateCaptcha(AjaxRequestTarget target) { CaptchaPanel captcha = getCaptcha(); if (captcha.getRandomText() == null) { String message = createStringResource("PageSelfRegistration.captcha.validation.failed") .getString(); LOGGER.error(message); getSession().error(message); target.add(getFeedbackPanel()); updateCaptcha(target); return false; } if (captcha.getCaptchaText() != null && captcha.getRandomText() != null) { if (!captcha.getCaptchaText().equals(captcha.getRandomText())) { String message = createStringResource("PageSelfRegistration.captcha.validation.failed") .getString(); LOGGER.error(message); getSession().error(message); updateCaptcha(target); target.add(getFeedbackPanel()); return false; } } LOGGER.trace("CAPTCHA Validation OK"); return true; } private void updateCaptcha(AjaxRequestTarget target) { CaptchaPanel captcha = new CaptchaPanel(ID_CAPTCHA); captcha.setOutputMarkupId(true); Form<?> form = (Form<?>) get(ID_MAIN_FORM); form.addOrReplace(captcha); target.add(form); } private void saveUser(Task task, OperationResult result) { ObjectDelta<UserType> userDelta; try { userDelta = prepareUserDelta(task, result); } catch (SchemaException | ExpressionEvaluationException | ObjectNotFoundException e) { result.recordFatalError("Failed to create delta for user: " + e.getMessage(), e); return; } userDelta.setPrismContext(getPrismContext()); WebModelServiceUtils.save(userDelta, ModelExecuteOptions.createOverwrite(), result, task, PageSelfRegistration.this); result.computeStatus(); } private ObjectDelta<UserType> prepareUserDelta(Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException { if (getOidFromParams(getPageParameters()) == null) { LOGGER.trace("Preparing user ADD delta (new user registration)"); UserType userType = prepareUserToSave(task, result); ObjectDelta<UserType> userDelta = ObjectDelta.createAddDelta(userType.asPrismObject()); LOGGER.trace("Going to register user {}", userDelta); return userDelta; } else { LOGGER.trace("Preparing user MODIFY delta (preregistered user registration)"); ObjectDelta<UserType> delta = null; if (getSelfRegistrationConfiguration().getFormRef() == null) { delta = ObjectDelta.createEmptyModifyDelta(UserType.class, getOidFromParams(getPageParameters()), getPrismContext()); if (getSelfRegistrationConfiguration().getInitialLifecycleState() != null) { delta.addModificationReplaceProperty(UserType.F_LIFECYCLE_STATE, getSelfRegistrationConfiguration().getInitialLifecycleState()); } delta.addModificationReplaceProperty(SchemaConstants.PATH_PASSWORD_VALUE, createPassword().getValue()); } else { delta = getDynamicFormPanel().getObjectDelta(); } delta.addModificationReplaceContainer(SchemaConstants.PATH_NONCE, createNonce(getSelfRegistrationConfiguration().getNoncePolicy(), task, result) .asPrismContainerValue()); LOGGER.trace("Going to register user with modifications {}", delta); return delta; } } private UserType prepareUserToSave(Task task, OperationResult result) throws ExpressionEvaluationException, SchemaException, ObjectNotFoundException { SelfRegistrationDto selfRegistrationConfiguration = getSelfRegistrationConfiguration(); UserType userType = userModel.getObject(); UserType userToSave = userType.clone(); if (selfRegistrationConfiguration.getFormRef() == null) { userType.clone(); if (selfRegistrationConfiguration.getRequiredLifecycleState() != null) { String userLifecycle = userToSave.getLifecycleState(); if (!selfRegistrationConfiguration.getRequiredLifecycleState().equals(userLifecycle)) { LOGGER.error( "Registration not allowed for a user {} -> Unsatisfied Configuration for required lifecycle, expected {} but was {}", new Object[] { userToSave.getEmailAddress() != null ? userToSave.getEmailAddress() : userToSave, selfRegistrationConfiguration.getRequiredLifecycleState(), userLifecycle }); getSession().error(createStringResource( "PageSelfRegistration.registration.failed.unsatisfied.registration.configuration") .getString()); throw new RestartResponseException(this); } } } else { try { userToSave = getDynamicFormPanel().getObject().asObjectable().clone(); } catch (SchemaException e) { LoggingUtils.logException(LOGGER, "Failed to construct delta " + e.getMessage(), e); new RestartResponseException(this); } } // CredentialsType credentials = createCredentials(userToSave, selfRegistrationConfiguration.getNoncePolicy(), task, result); // userToSave.setCredentials(credentials); if (selfRegistrationConfiguration.getInitialLifecycleState() != null) { LOGGER.trace("Setting initial lifecycle state of registered user to {}", selfRegistrationConfiguration.getInitialLifecycleState()); userToSave.setLifecycleState(selfRegistrationConfiguration.getInitialLifecycleState()); } try { getPrismContext().adopt(userToSave); } catch (SchemaException e) { // nothing to do, try without it } return userToSave; } private DynamicFormPanel<UserType> getDynamicFormPanel() { return (DynamicFormPanel<UserType>) get( createComponentPath(ID_MAIN_FORM, ID_DYNAMIC_FORM, ID_DYNAMIC_FORM_PANEL)); } private void createCredentials(UserType user, NonceCredentialsPolicyType noncePolicy, Task task, OperationResult result) throws ExpressionEvaluationException, SchemaException, ObjectNotFoundException { NonceType nonceType = createNonce(noncePolicy, task, result); // PasswordType password = createPassword(); CredentialsType credentials = user.getCredentials(); if (user.getCredentials() == null) { credentials = new CredentialsType(); user.setCredentials(credentials); } credentials.setNonce(nonceType); // credentials.setPassword(password); // return credentials; } private NonceType createNonce(NonceCredentialsPolicyType noncePolicy, Task task, OperationResult result) throws ExpressionEvaluationException, SchemaException, ObjectNotFoundException { ProtectedStringType nonceCredentials = new ProtectedStringType(); nonceCredentials.setClearValue(generateNonce(noncePolicy, null, task, result)); NonceType nonceType = new NonceType(); nonceType.setValue(nonceCredentials); return nonceType; } private PasswordType createPassword() { PasswordType password = new PasswordType(); ProtectedStringType protectedString = new ProtectedStringType(); protectedString.setClearValue(getPassword()); password.setValue(protectedString); return password; } private <O extends ObjectType> String generateNonce(NonceCredentialsPolicyType noncePolicy, PrismObject<O> user, Task task, 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(), PageSelfRegistration.this, task, result); policy = valuePolicy.asObjectable(); } return getModelInteractionService().generateValue(policy != null ? policy.getStringPolicy() : null, 24, false, user, "nonce generation (registration)", task, result); } private String getPassword() { PasswordPanel password = (PasswordPanel) get(createComponentPath(ID_MAIN_FORM, ID_STATIC_FORM, ID_PASSWORD)); return (String) password.getBaseFormComponent().getModel().getObject(); } // private void removePassword(AjaxRequestTarget target) { // PasswordPanel password = (PasswordPanel) // get(createComponentPath(ID_MAIN_FORM, ID_PASSWORD)); // for (FormComponent comp : password.getFormComponents()) { // comp.getModel().setObject(null); // } // target.add(password); // } @Override protected void createBreadcrumb() { // don't create breadcrumb for registration page } }