diff --git a/keycloak-phone-provider.resources/src/main/resources/theme/phone/login/messages/messages_de.properties b/keycloak-phone-provider.resources/src/main/resources/theme/phone/login/messages/messages_de.properties new file mode 100644 index 00000000..e04c7e79 --- /dev/null +++ b/keycloak-phone-provider.resources/src/main/resources/theme/phone/login/messages/messages_de.properties @@ -0,0 +1,34 @@ +updatePhoneNumber=Telefonnummer \u00E4ndern +configSms2Fa=SMS-2FA konfigurieren +authCodePhoneNumber=Authentifizierungscode +updatePhoneNumberInfo=Bitte Mobilnummer eingeben und auf Senden klicken, um einen Code zu erhalten. Dieser Code beweist, dass Sie die Nummer besitzen. +configSms2FaInfo=Bitte Mobilnummer eingeben und auf Senden klicken, um einen Code zu erhalten. Dieser Code beweist, dass Sie die Nummer besitzen. +authCodeInfo=Geben Sie den Authentifizierungscode ein, den Sie auf Ihrem Mobilger\u00E4t erhalten haben. +phoneNumber=Telefonnummer +requiredPhoneNumber=Telefonnummer ist erforderlich. +sendVerificationCode=Code senden +verificationCode=Best\u00E4tigungscode +authenticationCode=Authentifizierungscode +noOngoingVerificationProcess=Es l\u00E4uft aktuell kein Verifikationsprozess. +verificationCodeDoesNotMatch=Der eingegebene Best\u00E4tigungscode stimmt nicht mit unserem \u00FCberein. +phoneTokenCodeDoesNotMatch=Der eingegebene Authentifizierungscode stimmt nicht mit unserem \u00FCberein. +abusedMessageService=Zu viele Code-Anfragen f\u00FCr Ihre Mobilnummer. +sendVerificationCodeFail=Fehler beim Senden des Best\u00E4tigungscodes! +invalidPhoneNumber=Telefonnummer ist ung\u00FCltig +invalidPhoneNumberCountryCode=Die angegebene L\u00E4ndervorwahl geh\u00F6rt nicht zu einem unterst\u00FCtzten Land oder einer nicht-geografischen Entit\u00E4t. +invalidPhoneNumberTooLong=Telefonnummer ist ung\u00FCltig, zu lang! +invalidPhoneNumberMustNumber=Telefonnummer ist ung\u00FCltig, muss eine Nummer sein! +invalidPhoneNumberTooShort=Telefonnummer ist ung\u00FCltig, zu kurz! +invalidPhoneNumberNotSupported=Telefonnummer ist ung\u00FCltig, wird nicht unterst\u00FCtzt! +phoneNumberExists=Telefonnummer ist bereits registriert. +requiredVerificationCode=Best\u00E4tigungscode ist erforderlich. +phoneInstruction=Bitte den Telefon-Best\u00E4tigungscode verwenden, um das Passwort zur\u00FCckzusetzen. +phoneUserNotFound=Benutzer existiert nicht. +codeSent=OTP-Code wurde an {0} gesendet. +duplicatePhoneAllowedCantLogin = Telefon-Duplikat ist erlaubt, daher kann die Anmeldung per Telefon nicht verwendet werden. \u00C4ndere --spi-phone-support-default-<$realm>-duplicate-phone-allowed auf "false". +loginByPhone=Telefon +loginByPassword=Passwort +usernameOrPhoneNumber=Benutzername oder Telefon +usernameOrEmailOrPhoneNumber=Benutzername oder E-Mail oder Telefon +emailOrPhoneNumber=E-Mail oder Telefon +smsCodeMessage=\u005B{0}\u005D - {1} code: {2}, l\u00E4uft ab in {3} Minuten diff --git a/keycloak-phone-provider.resources/src/main/resources/theme/phone/login/messages/messages_en.properties b/keycloak-phone-provider.resources/src/main/resources/theme/phone/login/messages/messages_en.properties index 5d58d5dd..ff572d30 100644 --- a/keycloak-phone-provider.resources/src/main/resources/theme/phone/login/messages/messages_en.properties +++ b/keycloak-phone-provider.resources/src/main/resources/theme/phone/login/messages/messages_en.properties @@ -33,4 +33,5 @@ loginByPassword=Password usernameOrPhoneNumber=Username or phone usernameOrEmailOrPhoneNumber=Username or email or phone -emailOrPhoneNumber=Email or phone \ No newline at end of file +emailOrPhoneNumber=Email or phone +smsCodeMessage=\u005B{0}\u005D - {1} code: {2}, expires: {3} minute \ No newline at end of file diff --git a/keycloak-phone-provider/src/main/java/cc/coopersoft/keycloak/phone/providers/spi/FullSmsSenderAbstractService.java b/keycloak-phone-provider/src/main/java/cc/coopersoft/keycloak/phone/providers/spi/FullSmsSenderAbstractService.java index d6fe46a4..d9fc514e 100644 --- a/keycloak-phone-provider/src/main/java/cc/coopersoft/keycloak/phone/providers/spi/FullSmsSenderAbstractService.java +++ b/keycloak-phone-provider/src/main/java/cc/coopersoft/keycloak/phone/providers/spi/FullSmsSenderAbstractService.java @@ -1,24 +1,92 @@ package cc.coopersoft.keycloak.phone.providers.spi; +import cc.coopersoft.keycloak.phone.Utils; import cc.coopersoft.keycloak.phone.providers.constants.TokenCodeType; import cc.coopersoft.keycloak.phone.providers.exception.MessageSendException; + +import java.text.MessageFormat; +import java.util.Locale; +import java.util.Optional; +import java.util.Properties; + +import org.jboss.logging.Logger; import org.keycloak.models.KeycloakSession; +import org.keycloak.models.UserModel; +import org.keycloak.theme.Theme; -public abstract class FullSmsSenderAbstractService implements MessageSenderService{ +public abstract class FullSmsSenderAbstractService implements MessageSenderService { + private static final Logger logger = Logger.getLogger(FullSmsSenderAbstractService.class); private final String realmDisplay; + private final KeycloakSession session; + + @Deprecated public FullSmsSenderAbstractService(String realmDisplay) { this.realmDisplay = realmDisplay; + this.session = null; } - public abstract void sendMessage(String phoneNumber, String message) throws MessageSendException; + public FullSmsSenderAbstractService(KeycloakSession session) { + this.session = session; + this.realmDisplay = session.getContext().getRealm().getDisplayName(); + } + public abstract void sendMessage(String phoneNumber, String message) throws MessageSendException; @Override - public void sendSmsMessage(TokenCodeType type, String phoneNumber, String code , int expires, String kind) throws MessageSendException{ - //TODO template from keycloak message bundle - final String MESSAGE = String.format("[%s] - " + type.label + " code: %s, expires: %s minute ",realmDisplay , code, expires / 60); - sendMessage(phoneNumber,MESSAGE); + public void sendSmsMessage(TokenCodeType type, String phoneNumber, String code, int expires, String kind) + throws MessageSendException { + final String defaultMessage = String.format("[%s] - " + type.label + " code: %s, expires: %s minute ", + realmDisplay, code, expires / 60); + final String MESSAGE = localizeMessage(type, phoneNumber, code, expires).orElse(defaultMessage); + sendMessage(phoneNumber, MESSAGE); + } + + /** + * Localizes sms code message template from login theme. + * + * @param type the type of code sent + * @param phoneNumber the user's phone number (if applicable) + * @param code the verification code + * @param expires code expiration in seconds + * @return The localized string, else empty. + */ + private Optional localizeMessage(TokenCodeType type, String phoneNumber, String code, int expires) { + if (this.session != null) { + try { + // Get login theme + final String loginThemeName = session.getContext().getRealm().getLoginTheme(); + final Theme loginTheme = session.theme().getTheme(loginThemeName, Theme.Type.LOGIN); + + // Try get locale from user associated with phone number (if any) + final Optional user = Utils.findUserByPhone(session, session.getContext().getRealm(), + phoneNumber); + final Optional userLocale = user.map(u -> u.getFirstAttribute(UserModel.LOCALE)); + + // Use locale from user or default to realm locale + final String localeName = userLocale.isPresent() ? userLocale.get() + : session.getContext().getRealm().getDefaultLocale(); + final Locale locale = Locale.forLanguageTag(localeName); + + // Get message template from login theme bundle + final Properties messages = loginTheme.getMessages(locale); + final String messageTemplate = messages.getProperty("smsCodeMessage"); + + // Return nothing when template can't be found + if (messageTemplate == null || messageTemplate.isBlank()) { + return Optional.empty(); + } + + // Format message + MessageFormat mf = new MessageFormat(messageTemplate, locale); + return Optional.of(mf.format(new Object[] { realmDisplay, type.label, code, expires / 60 })); + } catch (Exception ex) { + logger.error("Error while trying to localize message", ex); + return Optional.empty(); + } + } + + return Optional.empty(); } } diff --git a/keycloak-sms-provider-bulksms/src/main/java/cc/coopersoft/keycloak/phone/providers/sender/BulksmsMessageSenderServiceProviderFactory.java b/keycloak-sms-provider-bulksms/src/main/java/cc/coopersoft/keycloak/phone/providers/sender/BulksmsMessageSenderServiceProviderFactory.java index 686fd059..80c849f8 100644 --- a/keycloak-sms-provider-bulksms/src/main/java/cc/coopersoft/keycloak/phone/providers/sender/BulksmsMessageSenderServiceProviderFactory.java +++ b/keycloak-sms-provider-bulksms/src/main/java/cc/coopersoft/keycloak/phone/providers/sender/BulksmsMessageSenderServiceProviderFactory.java @@ -12,7 +12,7 @@ public class BulksmsMessageSenderServiceProviderFactory implements MessageSender @Override public MessageSenderService create(KeycloakSession session) { - return new BulksmsSmsSenderServiceProvider(config,session.getContext().getRealm().getDisplayName()); + return new BulksmsSmsSenderServiceProvider(config, session); } @Override diff --git a/keycloak-sms-provider-bulksms/src/main/java/cc/coopersoft/keycloak/phone/providers/sender/BulksmsSmsSenderServiceProvider.java b/keycloak-sms-provider-bulksms/src/main/java/cc/coopersoft/keycloak/phone/providers/sender/BulksmsSmsSenderServiceProvider.java index 7a8bc124..11f9f073 100644 --- a/keycloak-sms-provider-bulksms/src/main/java/cc/coopersoft/keycloak/phone/providers/sender/BulksmsSmsSenderServiceProvider.java +++ b/keycloak-sms-provider-bulksms/src/main/java/cc/coopersoft/keycloak/phone/providers/sender/BulksmsSmsSenderServiceProvider.java @@ -6,6 +6,7 @@ import org.apache.http.impl.client.HttpClients; import org.jboss.logging.Logger; import org.keycloak.broker.provider.util.SimpleHttp; +import org.keycloak.models.KeycloakSession; import org.keycloak.Config.Scope; import java.io.IOException; @@ -44,8 +45,8 @@ public BulksmsMessage(String from, String to, String body, String encoding, Stri } } - BulksmsSmsSenderServiceProvider(Scope config, String realmDisplay) { - super(realmDisplay); + BulksmsSmsSenderServiceProvider(Scope config, KeycloakSession session) { + super(session); String configUrl = config.get(CONFIG_API_SERVER); this.url = configUrl != null ? configUrl : "https://api.bulksms.com/v1/messages";