Show a message when confirming an invitation link
Closes #29794 Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
parent
4c8abfb61f
commit
2d4d32764c
7 changed files with 32 additions and 6 deletions
|
@ -25,6 +25,7 @@ import java.util.stream.Stream;
|
||||||
public interface OrganizationModel {
|
public interface OrganizationModel {
|
||||||
|
|
||||||
String ORGANIZATION_ATTRIBUTE = "kc.org";
|
String ORGANIZATION_ATTRIBUTE = "kc.org";
|
||||||
|
String ORGANIZATION_NAME_ATTRIBUTE = "kc.org.name";
|
||||||
String ORGANIZATION_DOMAIN_ATTRIBUTE = "kc.org.domain";
|
String ORGANIZATION_DOMAIN_ATTRIBUTE = "kc.org.domain";
|
||||||
String BROKER_PUBLIC = "kc.org.broker.public";
|
String BROKER_PUBLIC = "kc.org.broker.public";
|
||||||
|
|
||||||
|
|
|
@ -43,8 +43,8 @@ import org.keycloak.services.messages.Messages;
|
||||||
import org.keycloak.sessions.AuthenticationSessionCompoundId;
|
import org.keycloak.sessions.AuthenticationSessionCompoundId;
|
||||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
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}.
|
* 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)
|
.setAuthenticationSession(authSession)
|
||||||
.setSuccess(Messages.CONFIRM_EXECUTION_OF_ACTIONS)
|
.setSuccess(Messages.CONFIRM_EXECUTION_OF_ACTIONS)
|
||||||
.setAttribute(Constants.TEMPLATE_ATTR_ACTION_URI, confirmUri)
|
.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();
|
.createInfoPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -404,7 +404,9 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
||||||
Properties messagesBundle;
|
Properties messagesBundle;
|
||||||
try {
|
try {
|
||||||
messagesBundle = theme.getEnhancedMessages(realm, locale);
|
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));
|
attributes.put("advancedMsg", new AdvancedMessageFormatterMethod(locale, messagesBundle));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.warn("Failed to load messages", e);
|
logger.warn("Failed to load messages", e);
|
||||||
|
@ -441,7 +443,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
||||||
}
|
}
|
||||||
attributes.put("message", wholeMessage);
|
attributes.put("message", wholeMessage);
|
||||||
} else {
|
} else {
|
||||||
attributes.put("message", null);
|
attributes.remove("message");
|
||||||
}
|
}
|
||||||
attributes.put("messagesPerField", messagesPerField);
|
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("url", new UrlBean(realm, theme, baseUri, this.actionUri));
|
||||||
attributes.put("requiredActionUrl", new RequiredActionUrlFormatterMethod(realm, baseUri));
|
attributes.put("requiredActionUrl", new RequiredActionUrlFormatterMethod(realm, baseUri));
|
||||||
attributes.put("auth", new AuthenticationContextBean(context, page));
|
attributes.put("auth", new AuthenticationContextBean(context, page));
|
||||||
attributes.put(Constants.EXECUTION, execution);
|
setAttribute(Constants.EXECUTION, execution);
|
||||||
|
|
||||||
if (realm.isInternationalizationEnabled()) {
|
if (realm.isInternationalizationEnabled()) {
|
||||||
UriBuilder b;
|
UriBuilder b;
|
||||||
|
@ -893,7 +895,11 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LoginFormsProvider setAttribute(String name, Object value) {
|
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;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -320,4 +320,5 @@ public class Messages {
|
||||||
public static final String OAUTH2_DEVICE_VERIFICATION_FAILED_HEADER = "oauth2DeviceVerificationFailedHeader";
|
public static final String OAUTH2_DEVICE_VERIFICATION_FAILED_HEADER = "oauth2DeviceVerificationFailedHeader";
|
||||||
public static final String OAUTH2_DEVICE_CONSENT_DENIED = "oauth2DeviceConsentDeniedMessage";
|
public static final String OAUTH2_DEVICE_CONSENT_DENIED = "oauth2DeviceConsentDeniedMessage";
|
||||||
|
|
||||||
|
public static final String CONFIRM_ORGANIZATION_MEMBERSHIP = "organization.confirm-membership";
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
|
|
||||||
package org.keycloak.theme.beans;
|
package org.keycloak.theme.beans;
|
||||||
|
|
||||||
|
import static java.util.Optional.ofNullable;
|
||||||
|
|
||||||
import freemarker.template.SimpleScalar;
|
import freemarker.template.SimpleScalar;
|
||||||
import freemarker.template.TemplateMethodModelEx;
|
import freemarker.template.TemplateMethodModelEx;
|
||||||
import freemarker.template.TemplateModelException;
|
import freemarker.template.TemplateModelException;
|
||||||
|
@ -26,6 +28,8 @@ import java.text.MessageFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -40,13 +44,22 @@ public class MessageFormatterMethod implements TemplateMethodModelEx {
|
||||||
this.messages = messages;
|
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
|
@Override
|
||||||
public Object exec(List list) throws TemplateModelException {
|
public Object exec(List list) throws TemplateModelException {
|
||||||
if (list.size() >= 1) {
|
if (list.size() >= 1) {
|
||||||
// resolve any remaining ${} expressions
|
// resolve any remaining ${} expressions
|
||||||
List<Object> resolved = resolve(list.subList(1, list.size()));
|
List<Object> resolved = resolve(list.subList(1, list.size()));
|
||||||
String key = list.get(0).toString();
|
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 {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -223,6 +223,7 @@ public class OrganizationInvitationLinkTest extends AbstractOrganizationTest {
|
||||||
// not yet a member
|
// not yet a member
|
||||||
Assert.assertFalse(organization.members().getAll().stream().anyMatch(actual -> user.getId().equals(actual.getId())));
|
Assert.assertFalse(organization.members().getAll().stream().anyMatch(actual -> user.getId().equals(actual.getId())));
|
||||||
// confirm the intent of membership
|
// confirm the intent of membership
|
||||||
|
assertThat(infoPage.getInfo(), containsString("You are about to join organization " + organizationName));
|
||||||
infoPage.clickToContinue();
|
infoPage.clickToContinue();
|
||||||
assertThat(infoPage.getInfo(), containsString("Your account has been updated."));
|
assertThat(infoPage.getInfo(), containsString("Your account has been updated."));
|
||||||
// now a member
|
// now a member
|
||||||
|
|
|
@ -517,3 +517,5 @@ doLogout=Logout
|
||||||
|
|
||||||
readOnlyUsernameMessage=You can''t update your username as it is read-only.
|
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).
|
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}
|
Loading…
Reference in a new issue