Skip to content

Commit

Permalink
fix for security issue on the self registration page
Browse files Browse the repository at this point in the history
  • Loading branch information
Kateryna Honchar committed May 25, 2023
1 parent d225fe8 commit c90cf73
Show file tree
Hide file tree
Showing 15 changed files with 399 additions and 95 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* Copyright (C) 2010-2023 Evolveum and contributors
*
* This work is dual-licensed under the Apache License 2.0
* and European Union Public License. See LICENSE file for details.
*/
package com.evolveum.midpoint.web.page.login;

import com.evolveum.midpoint.web.application.AuthorizationAction;
import com.evolveum.midpoint.web.application.PageDescriptor;
import com.evolveum.midpoint.web.application.Url;

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.util.SecurityPolicyUtil;
import com.evolveum.midpoint.security.api.AuthorizationConstants;
import com.evolveum.midpoint.security.api.SecurityUtil;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.util.exception.*;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;

@PageDescriptor(urls = { @Url(mountUrl = "/invitation", matchUrlForSecurity = "/invitation") },
action = {
@AuthorizationAction(actionUri = AuthorizationConstants.AUTZ_UI_INVITATION_URL) })
public class PageInvitation extends PageSelfRegistration {

private static final long serialVersionUID = 1L;

private static final Trace LOGGER = TraceManager.getTrace(PageInvitation.class);

private static final String DOT_CLASS = PageInvitation.class.getName() + ".";

public PageInvitation() {
super();
}

@Override
protected UserType instantiateUser() {
return (UserType) getPrincipalFocus();
}

@Override
protected ObjectDelta<UserType> prepareUserDelta(Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException {
LOGGER.trace("Preparing user MODIFY delta (preregistered user registration)");
ObjectDelta<UserType> delta;
if (!isCustomFormDefined()) {
delta = getPrismContext().deltaFactory().object().createEmptyModifyDelta(UserType.class,
userModel.getObject().getOid());
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(getNonceCredentialsPolicy(), task, result).asPrismContainerValue());
LOGGER.trace("Going to register user with modifications {}", delta);
return delta;
}

private NonceCredentialsPolicyType getNonceCredentialsPolicy() {
SecurityPolicyType securityPolicy = resolveSecurityPolicy();
if (securityPolicy == null) {
return null;
}
String invitationSequenceIdentifier = SecurityUtil.getInvitationSequenceName(securityPolicy);
AuthenticationSequenceType invitationSequence = SecurityPolicyUtil.findSequenceByName(securityPolicy, invitationSequenceIdentifier);
if (invitationSequence == null || invitationSequence.getModule().isEmpty()) {
return null;
}
String moduleIdentifier = invitationSequence.getModule().get(0).getName();
if (moduleIdentifier == null) {
return null;
}
MailNonceAuthenticationModuleType nonceModule = securityPolicy
.getAuthentication()
.getModules()
.getMailNonce()
.stream()
.filter(m -> moduleIdentifier.equals(m.getName()))
.findFirst()
.orElse(null);
if (nonceModule == null) {
return null;
}
String credentialName = nonceModule.getCredentialName();
if (credentialName == null) {
return null;
}
return securityPolicy
.getCredentials()
.getNonce()
.stream()
.filter(n -> credentialName.equals(n.getName()))
.findFirst()
.orElse(null);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ private void initResetCredentialsConfiguration() {

}

private SecurityPolicyType resolveSecurityPolicy() {
protected SecurityPolicyType resolveSecurityPolicy() {
SecurityPolicyType securityPolicy = resolveSecurityPolicy(null);

if (securityPolicy == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ <h3 wicket:id="welcome"/>
<div>
<label wicket:id="additionalText"></label>
</div>
<div wicket:id="feedback"></div>

<table class="table table-striped">
<tr>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,75 +77,25 @@ public class PageSelfRegistration extends PageAbstractFlow {

private static final String ID_STATIC_FORM = "staticForm";

private static final String PARAM_USER_OID = "user";
protected IModel<UserType> userModel;

private IModel<UserType> userModel;

public PageSelfRegistration(PageParameters pageParameters) {
super(pageParameters);
}

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;
public PageSelfRegistration() {
super(null);
}

@Override
public void initializeModel() {
final String userOid = getOidFromParams(pageParameters);

userModel = new LoadableModel<UserType>(false) {
userModel = new LoadableModel<>(false) {
private static final long serialVersionUID = 1L;

@Override
protected UserType load() {
return createUserModel(userOid);
return instantiateUser();
}
};
}

private UserType createUserModel(String userOid) {
if (userOid == null) {
LOGGER.trace("Registration process for new user started");
return instantiateUser();
}

PrismObject<UserType> result = runPrivileged(new Producer<PrismObject<UserType>>() {

private static final long serialVersionUID = 1L;

@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();
}

private UserType instantiateUser() {
protected UserType instantiateUser() {
PrismObjectDefinition<UserType> userDef = getUserDefinition();
PrismObject<UserType> user;
try {
Expand Down Expand Up @@ -176,7 +126,7 @@ protected WebMarkupContainer initStaticLayout() {
FeedbackPanel feedback = new FeedbackPanel(ID_FEEDBACK,
new ContainerFeedbackMessageFilter(PageSelfRegistration.this));
feedback.setOutputMarkupId(true);
add(feedback);
staticRegistrationForm.add(feedback);

TextPanel<String> firstName = new TextPanel<>(ID_FIRST_NAME,
new PropertyModel<String>(getUserModel(), UserType.F_GIVEN_NAME.getLocalPart() + ".orig") {
Expand Down Expand Up @@ -240,7 +190,7 @@ private void initInputProperties(FeedbackPanel feedback, TextPanel<String> input

@Override
public boolean isEnabled() {
return getOidFromParams(getPageParameters()) == null;
return getUserModel().getObject() == null;
}

});
Expand Down Expand Up @@ -382,35 +332,12 @@ private void saveUser(OperationResult result) {
}
}

private ObjectDelta<UserType> prepareUserDelta(Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException {
if (getOidFromParams(getPageParameters()) == null) {
LOGGER.trace("Preparing user ADD delta (new user registration)");
UserType userType = prepareUserToSave(task, result);
ObjectDelta<UserType> userDelta = DeltaFactory.Object.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;
if (!isCustomFormDefined()) {
delta = getPrismContext().deltaFactory().object().createEmptyModifyDelta(UserType.class,
getOidFromParams(getPageParameters()));
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;

}
protected ObjectDelta<UserType> prepareUserDelta(Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException {
LOGGER.trace("Preparing user ADD delta (new user registration)");
UserType userType = prepareUserToSave(task, result);
ObjectDelta<UserType> userDelta = DeltaFactory.Object.createAddDelta(userType.asPrismObject());
LOGGER.trace("Going to register user {}", userDelta);
return userDelta;
}

private UserType prepareUserToSave(Task task, OperationResult result) throws ExpressionEvaluationException, SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException {
Expand Down Expand Up @@ -485,7 +412,7 @@ private UserType prepareUserToSave(Task task, OperationResult result) throws Exp
//
// }

private NonceType createNonce(NonceCredentialsPolicyType noncePolicy, Task task, OperationResult result) throws ExpressionEvaluationException, SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException {
protected NonceType createNonce(NonceCredentialsPolicyType noncePolicy, Task task, OperationResult result) throws ExpressionEvaluationException, SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException {
ProtectedStringType nonceCredentials = new ProtectedStringType();
nonceCredentials.setClearValue(generateNonce(noncePolicy, null, task, result));

Expand Down Expand Up @@ -513,7 +440,7 @@ private CredentialsType getCredentials(UserType user) {
return credentials;
}

private PasswordType createPassword() {
protected PasswordType createPassword() {
PasswordType password = new PasswordType();
ProtectedStringType protectedString = new ProtectedStringType();
protectedString.setClearValue(getPassword());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright (C) 2010-2023 Evolveum and contributors
*
* This work is dual-licensed under the Apache License 2.0
* and European Union Public License. See LICENSE file for details.
*/
package com.evolveum.midpoint.web.security.channel;

import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.security.api.Authorization;
import com.evolveum.midpoint.security.api.AuthorizationConstants;
import com.evolveum.midpoint.xml.ns._public.common.common_3.AuthenticationSequenceChannelType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.AuthorizationType;

import java.util.ArrayList;
import java.util.Collection;

/**
* @author skublik
*/

public class InvitationAuthenticationChannel extends AuthenticationChannelImpl {

public InvitationAuthenticationChannel(AuthenticationSequenceChannelType channel) {
super(channel);
}

public String getChannelId() {
return SchemaConstants.CHANNEL_INVITATION_URI;
}

public String getPathAfterSuccessfulAuthentication() {
return "/invitation";
}

public String getPathAfterUnsuccessfulAuthentication() {
return "/";
}

@Override
public String getSpecificLoginUrl() {
return "/invitation";
}

@Override
public boolean isSupportActivationByChannel() {
return false;
}

@Override
public Collection<Authorization> resolveAuthorities(Collection<Authorization> authorities) {
ArrayList<Authorization> newAuthorities = new ArrayList<>();
AuthorizationType authorizationBean = new AuthorizationType();
authorizationBean.getAction().add(AuthorizationConstants.AUTZ_UI_INVITATION_URL);
Authorization selfServiceCredentialsAuthz = new Authorization(authorizationBean);
newAuthorities.add(selfServiceCredentialsAuthz);
authorities.addAll(newAuthorities);
return authorities;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (C) 2010-2023 Evolveum and contributors
*
* This work is dual-licensed under the Apache License 2.0
* and European Union Public License. See LICENSE file for details.
*/
package com.evolveum.midpoint.web.security.factory.channel;

import com.evolveum.midpoint.model.api.authentication.AuthenticationChannel;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.web.security.channel.InvitationAuthenticationChannel;
import com.evolveum.midpoint.xml.ns._public.common.common_3.AuthenticationSequenceChannelType;

import org.springframework.stereotype.Component;

@Component
public class InvitationChannelFactory extends AbstractChannelFactory {
@Override
public boolean match(String channelId) {
return SchemaConstants.CHANNEL_INVITATION_URI.equals(channelId);
}

@Override
public AuthenticationChannel createAuthChannel(AuthenticationSequenceChannelType channel) {
return new InvitationAuthenticationChannel(channel);
}

@Override
protected Integer getOrder() {
return 10;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ public static GuiProfiledPrincipal getPrincipalUser() {
.put("actuator", SchemaConstants.CHANNEL_ACTUATOR_URI)
.put("resetPassword", SchemaConstants.CHANNEL_RESET_PASSWORD_URI)
.put("registration", SchemaConstants.CHANNEL_SELF_REGISTRATION_URI)
.put("invitation", SchemaConstants.CHANNEL_INVITATION_URI)
.build();

LOGIN_URL_AND_TYPE = ImmutableMap.<String, Set<String>>builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,9 @@ public abstract class SchemaConstants {
public static final QName CHANNEL_SELF_REGISTRATION_QNAME = new QName(NS_CHANNEL, "selfRegistration");
public static final String CHANNEL_SELF_REGISTRATION_URI = qNameToUri(CHANNEL_SELF_REGISTRATION_QNAME);

public static final QName CHANNEL_INVITATION_QNAME = new QName(NS_CHANNEL, "invitation");
public static final String CHANNEL_INVITATION_URI = qNameToUri(CHANNEL_INVITATION_QNAME);

// Channel for self-service part of the user interface. These are the pages when user is changing his own data.
// E.g. update of his own profile and password change are considered to be self-service.
public static final QName CHANNEL_SELF_SERVICE_QNAME = new QName(NS_CHANNEL, "selfService");
Expand Down

0 comments on commit c90cf73

Please sign in to comment.