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 24, 2023
1 parent 8a888a0 commit 021fdf3
Show file tree
Hide file tree
Showing 14 changed files with 386 additions and 101 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* 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.gui.impl.page.login;

import com.evolveum.midpoint.authentication.api.authorization.AuthorizationAction;
import com.evolveum.midpoint.authentication.api.authorization.PageDescriptor;
import com.evolveum.midpoint.authentication.api.authorization.Url;
import com.evolveum.midpoint.authentication.api.util.AuthenticationModuleNameConstants;
import com.evolveum.midpoint.gui.api.model.LoadableModel;
import com.evolveum.midpoint.prism.delta.DeltaFactory;
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.*;
import org.apache.wicket.model.IModel;

@PageDescriptor(urls = { @Url(mountUrl = "/invitation", matchUrlForSecurity = "/invitation") },
action = {
@AuthorizationAction(actionUri = AuthorizationConstants.AUTZ_UI_INVITATION_URL) },
authModule = AuthenticationModuleNameConstants.MAIL_NONCE)
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.getInvitationSequenceIdentifier(securityPolicy);
AuthenticationSequenceType invitationSequence = SecurityPolicyUtil.findSequenceByIdentifier(securityPolicy, invitationSequenceIdentifier);
if (invitationSequence == null || invitationSequence.getModule().isEmpty()) {
return null;
}
String moduleIdentifier = invitationSequence.getModule().get(0).getIdentifier();
if (moduleIdentifier == null) {
return null;
}
MailNonceAuthenticationModuleType nonceModule = securityPolicy
.getAuthentication()
.getModules()
.getMailNonce()
.stream()
.filter(m -> moduleIdentifier.equals(m.getIdentifier()))
.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 @@ -147,7 +147,7 @@ private void initPostAuthenticationConfiguration() {

}

private SecurityPolicyType resolveSecurityPolicy() {
protected SecurityPolicyType resolveSecurityPolicy() {
SecurityPolicyType securityPolicy = runPrivileged((Producer<SecurityPolicyType>) () -> {

Task task = createAnonymousTask(OPERATION_GET_SECURITY_POLICY);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,79 +70,25 @@ public class PageSelfRegistration extends PageAbstractFlow {
private static final String ID_COMPONENT_FEEDBACK = "componentFeedback";
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 && isNotAdministrator(oidValue.toString())) {
return oidValue.toString();
}

return null;
}

private boolean isNotAdministrator(String userOid) {
return !SystemObjectsType.USER_ADMINISTRATOR.value().equals(userOid);
public PageSelfRegistration() {
super(null);
}

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

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<>() {

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 @@ -219,7 +165,7 @@ private void initInputProperties(FeedbackPanel feedback, String placeholderKey,

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

});
Expand Down Expand Up @@ -334,35 +280,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 @@ -419,7 +342,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, task, result));

Expand Down Expand Up @@ -447,7 +370,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
Expand Up @@ -440,6 +440,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
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.authentication.impl.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.authentication.impl.factory.channel;

import com.evolveum.midpoint.authentication.api.AuthenticationChannel;
import com.evolveum.midpoint.authentication.impl.channel.InvitationAuthenticationChannel;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
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 @@ -555,7 +555,8 @@ public boolean isIgnoredLocalPath(HttpServletRequest httpRequest) {

public void initializeAuthenticationSequence(MidpointAuthentication mpAuthentication, HttpServletRequest httpRequest, TaskManager taskManager) {
if (mpAuthentication != null && AuthSequenceUtil.isLoginPage(httpRequest)) {
if (mpAuthentication.getAuthenticationChannel() != null && !mpAuthentication.getAuthenticationChannel().getChannelId().equals(AuthSequenceUtil.findChannelByRequest(httpRequest))
if (mpAuthentication.getAuthenticationChannel() != null && !mpAuthentication.getAuthenticationChannel()
.getChannelId().equals(AuthSequenceUtil.findChannelByRequest(httpRequest))
&& AuthSequenceUtil.getSequenceByPath(httpRequest, authenticationsPolicy, taskManager.getLocalNodeGroups()) == null) {
return;
}
Expand All @@ -564,7 +565,7 @@ public void initializeAuthenticationSequence(MidpointAuthentication mpAuthentica
this.sequence = AuthSequenceUtil.getSequenceByPath(httpRequest, authenticationsPolicy, taskManager.getLocalNodeGroups());
}

if (isEqualChannelIdForAuthenticatedUser(mpAuthentication, httpRequest)) {
if (sequence != null && isEqualChannelIdForAuthenticatedUser(mpAuthentication, httpRequest)) {
changeLogoutToNewSequence(mpAuthentication, httpRequest);
this.sequence = mpAuthentication.getSequence();
}
Expand Down

0 comments on commit 021fdf3

Please sign in to comment.