Show a message when confirming an invitation link

Closes #29794

Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
Pedro Igor 2024-05-22 18:33:11 -03:00 committed by Alexander Schwartz
parent 4c8abfb61f
commit 2d4d32764c
7 changed files with 32 additions and 6 deletions

View file

@ -25,6 +25,7 @@ import java.util.stream.Stream;
public interface OrganizationModel {
String ORGANIZATION_ATTRIBUTE = "kc.org";
String ORGANIZATION_NAME_ATTRIBUTE = "kc.org.name";
String ORGANIZATION_DOMAIN_ATTRIBUTE = "kc.org.domain";
String BROKER_PUBLIC = "kc.org.broker.public";

View file

@ -43,8 +43,8 @@ import org.keycloak.services.messages.Messages;
import org.keycloak.sessions.AuthenticationSessionCompoundId;
import org.keycloak.sessions.AuthenticationSessionModel;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
/**
* Action token handler for handling invitation of an existing user to an organization. A new user is handled in registration {@link org.keycloak.services.resources.LoginActionsService}.
@ -114,6 +114,8 @@ public class InviteOrgActionTokenHandler extends AbstractActionTokenHandler<Invi
.setAuthenticationSession(authSession)
.setSuccess(Messages.CONFIRM_EXECUTION_OF_ACTIONS)
.setAttribute(Constants.TEMPLATE_ATTR_ACTION_URI, confirmUri)
.setAttribute(Constants.TEMPLATE_ATTR_REQUIRED_ACTIONS, List.of(Messages.CONFIRM_ORGANIZATION_MEMBERSHIP))
.setAttribute(OrganizationModel.ORGANIZATION_NAME_ATTRIBUTE, organization.getName())
.createInfoPage();
}

View file

@ -404,7 +404,9 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
Properties messagesBundle;
try {
messagesBundle = theme.getEnhancedMessages(realm, locale);
attributes.put("msg", new MessageFormatterMethod(locale, messagesBundle));
Map<Object, Object> msgParams = new HashMap<>(attributes);
msgParams.putAll(messagesBundle);
attributes.put("msg", new MessageFormatterMethod(locale, msgParams));
attributes.put("advancedMsg", new AdvancedMessageFormatterMethod(locale, messagesBundle));
} catch (IOException e) {
logger.warn("Failed to load messages", e);
@ -441,7 +443,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
}
attributes.put("message", wholeMessage);
} else {
attributes.put("message", null);
attributes.remove("message");
}
attributes.put("messagesPerField", messagesPerField);
}
@ -486,7 +488,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
attributes.put("url", new UrlBean(realm, theme, baseUri, this.actionUri));
attributes.put("requiredActionUrl", new RequiredActionUrlFormatterMethod(realm, baseUri));
attributes.put("auth", new AuthenticationContextBean(context, page));
attributes.put(Constants.EXECUTION, execution);
setAttribute(Constants.EXECUTION, execution);
if (realm.isInternationalizationEnabled()) {
UriBuilder b;
@ -893,7 +895,11 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
@Override
public LoginFormsProvider setAttribute(String name, Object value) {
this.attributes.put(name, value);
if (value == null) {
attributes.remove(name);
} else {
attributes.put(name, value);
}
return this;
}

View file

@ -320,4 +320,5 @@ public class Messages {
public static final String OAUTH2_DEVICE_VERIFICATION_FAILED_HEADER = "oauth2DeviceVerificationFailedHeader";
public static final String OAUTH2_DEVICE_CONSENT_DENIED = "oauth2DeviceConsentDeniedMessage";
public static final String CONFIRM_ORGANIZATION_MEMBERSHIP = "organization.confirm-membership";
}

View file

@ -17,6 +17,8 @@
package org.keycloak.theme.beans;
import static java.util.Optional.ofNullable;
import freemarker.template.SimpleScalar;
import freemarker.template.TemplateMethodModelEx;
import freemarker.template.TemplateModelException;
@ -26,6 +28,8 @@ import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
/**
@ -40,13 +44,22 @@ public class MessageFormatterMethod implements TemplateMethodModelEx {
this.messages = messages;
}
public MessageFormatterMethod(Locale locale, Map<Object, Object> messages) {
this.locale = locale;
this.messages = new Properties();
this.messages.putAll(ofNullable(messages).orElse(Map.of()));
}
@Override
public Object exec(List list) throws TemplateModelException {
if (list.size() >= 1) {
// resolve any remaining ${} expressions
List<Object> resolved = resolve(list.subList(1, list.size()));
String key = list.get(0).toString();
return new MessageFormat(messages.getProperty(key,key),locale).format(resolved.toArray());
String value = messages.getOrDefault(key, key).toString();
// try to also resolve placeholders if present in the message bundle
value = (String) resolve(List.of(value)).get(0);
return new MessageFormat(value, locale).format(resolved.toArray());
} else {
return null;
}

View file

@ -223,6 +223,7 @@ public class OrganizationInvitationLinkTest extends AbstractOrganizationTest {
// not yet a member
Assert.assertFalse(organization.members().getAll().stream().anyMatch(actual -> user.getId().equals(actual.getId())));
// confirm the intent of membership
assertThat(infoPage.getInfo(), containsString("You are about to join organization " + organizationName));
infoPage.clickToContinue();
assertThat(infoPage.getInfo(), containsString("Your account has been updated."));
// now a member

View file

@ -517,3 +517,5 @@ doLogout=Logout
readOnlyUsernameMessage=You can''t update your username as it is read-only.
error-invalid-multivalued-size=Attribute {0} must have at least {1} and at most {2} value(s).
requiredAction.organization.confirm-membership=You are about to join organization ${kc.org.name}