Переглянути джерело

obfuscate/mask token destination addresses

Jason Rivard 8 роки тому
батько
коміт
a5c81c046d

+ 4 - 0
src/main/java/password/pwm/AppProperty.java

@@ -282,6 +282,10 @@ public enum AppProperty {
     SECURITY_DEFAULT_EPHEMERAL_HASH_ALG             ("security.defaultEphemeralHashAlg"),
     SEEDLIST_BUILTIN_PATH                           ("seedlist.builtin.path"),
     SMTP_SUBJECT_ENCODING_CHARSET                   ("smtp.subjectEncodingCharset"),
+    TOKEN_MASK_EMAIL_REGEX                          ("token.mask.email.regex"),
+    TOKEN_MASK_EMAIL_REPLACE                        ("token.mask.email.replace"),
+    TOKEN_MASK_SMS_REGEX                            ("token.mask.sms.regex"),
+    TOKEN_MASK_SMS_REPLACE                          ("token.mask.sms.replace"),
     TOKEN_MAX_UNIQUE_CREATE_ATTEMPTS                ("token.maxUniqueCreateAttempts"),
     TOKEN_RESEND_DELAY_MS                           ("token.resend.delayMS"),
     TOKEN_REMOVE_ON_CLAIM                           ("token.removeOnClaim"),

+ 62 - 0
src/main/java/password/pwm/bean/TokenDestinationItem.java

@@ -0,0 +1,62 @@
+package password.pwm.bean;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import password.pwm.config.Configuration;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.ldap.UserInfo;
+import password.pwm.util.ValueObfuscator;
+import password.pwm.util.java.StringUtil;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@Getter
+@AllArgsConstructor
+public class TokenDestinationItem {
+    private String id;
+    private String display;
+    private String value;
+    private Type type;
+
+    public enum Type {
+        sms,
+        email,
+    }
+
+    public static List<TokenDestinationItem> allFromConfig(final Configuration configuration, final UserInfo userInfo)
+            throws PwmUnrecoverableException
+    {
+        final ValueObfuscator valueObfuscator = new ValueObfuscator(configuration);
+        int counter = 0;
+
+        final List<TokenDestinationItem> results = new ArrayList<>();
+
+        {
+            final String smsValue = userInfo.getUserSmsNumber();
+            if (!StringUtil.isEmpty(smsValue)) {
+                results.add(new TokenDestinationItem(
+                        String.valueOf(++counter),
+                        valueObfuscator.maskPhone(smsValue),
+                        smsValue,
+                        Type.sms
+                ));
+            }
+        }
+
+        {
+            final String emailValue = userInfo.getUserEmailAddress();
+            if (!StringUtil.isEmpty(emailValue)) {
+                results.add(new TokenDestinationItem(
+                        String.valueOf(++counter),
+                        valueObfuscator.maskEmail(emailValue),
+                        emailValue,
+                        Type.email
+                ));
+            }
+        }
+
+        return Collections.unmodifiableList(results);
+    }
+}

+ 1 - 0
src/main/java/password/pwm/http/PwmRequestAttribute.java

@@ -77,6 +77,7 @@ public enum PwmRequestAttribute {
     ForgottenPasswordInstructions,
     ForgottenPasswordOtpRecord,
     ForgottenPasswordResendTokenEnabled,
+    ForgottenPasswordTokenDestItems,
 
     GuestCurrentExpirationDate,
     GuestMaximumExpirationDate,

+ 16 - 8
src/main/java/password/pwm/http/servlet/ActivateUserServlet.java

@@ -33,15 +33,15 @@ import password.pwm.bean.EmailItemBean;
 import password.pwm.bean.LocalSessionStateBean;
 import password.pwm.bean.LoginInfoBean;
 import password.pwm.bean.SmsItemBean;
+import password.pwm.bean.TokenDestinationItem;
 import password.pwm.bean.UserIdentity;
-import password.pwm.config.value.data.ActionConfiguration;
 import password.pwm.config.Configuration;
-import password.pwm.config.value.data.FormConfiguration;
-import password.pwm.util.form.FormUtility;
 import password.pwm.config.PwmSetting;
-import password.pwm.config.value.data.UserPermission;
 import password.pwm.config.option.MessageSendMethod;
 import password.pwm.config.profile.LdapProfile;
+import password.pwm.config.value.data.ActionConfiguration;
+import password.pwm.config.value.data.FormConfiguration;
+import password.pwm.config.value.data.UserPermission;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmDataValidationException;
 import password.pwm.error.PwmError;
@@ -70,6 +70,7 @@ import password.pwm.svc.token.TokenService;
 import password.pwm.svc.token.TokenType;
 import password.pwm.util.CaptchaUtility;
 import password.pwm.util.PostChangePasswordAction;
+import password.pwm.util.form.FormUtility;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroMachine;
@@ -668,8 +669,8 @@ public class ActivateUserServlet extends AbstractPwmServlet {
             throw new PwmUnrecoverableException(e.getErrorInformation());
         }
 
-        sendToken(pwmRequest, userIdentity, locale, outputDestTokenData.getEmail(), outputDestTokenData.getSms(), tokenKey);
-        activateUserBean.setTokenDisplayText(outputDestTokenData.getDisplayValue());
+        final String displayValue = sendToken(pwmRequest, userIdentity, locale, outputDestTokenData.getEmail(), outputDestTokenData.getSms(), tokenKey);
+        activateUserBean.setTokenDisplayText(displayValue);
         activateUserBean.setTokenIssued(true);
     }
 
@@ -712,7 +713,7 @@ public class ActivateUserServlet extends AbstractPwmServlet {
         this.advanceToNextStage(pwmRequest);
     }
 
-    private static void sendToken(
+    private static String sendToken(
             final PwmRequest pwmRequest,
             final UserIdentity userIdentity,
             final Locale userLocale,
@@ -730,7 +731,7 @@ public class ActivateUserServlet extends AbstractPwmServlet {
 
         final MacroMachine macroMachine = MacroMachine.forUser(pwmRequest, userIdentity);
 
-        TokenService.TokenSender.sendToken(
+        final List<TokenDestinationItem.Type> sentTypes = TokenService.TokenSender.sendToken(
                 pwmApplication,
                 null,
                 macroMachine,
@@ -741,6 +742,13 @@ public class ActivateUserServlet extends AbstractPwmServlet {
                 smsMessage,
                 tokenKey
         );
+
+        return TokenService.TokenSender.figureDisplayString(
+                pwmApplication.getConfig(),
+                sentTypes,
+                toAddress,
+                toSmsNumber
+                );
     }
 
     private static String figureLdapSearchFilter(final PwmRequest pwmRequest)

+ 11 - 1
src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordServlet.java

@@ -36,6 +36,7 @@ import password.pwm.VerificationMethodSystem;
 import password.pwm.bean.LoginInfoBean;
 import password.pwm.bean.PasswordStatus;
 import password.pwm.bean.SessionLabel;
+import password.pwm.bean.TokenDestinationItem;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
@@ -1381,7 +1382,7 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet {
                 }
 
                 if (progress.getTokenSendChoice() == MessageSendMethod.CHOICE_SMS_EMAIL) {
-                    pwmRequest.forwardToJsp(JspUrl.RECOVER_PASSWORD_TOKEN_CHOICE);
+                    forwardToTokenChoiceJsp(pwmRequest);
                     return;
                 }
 
@@ -1442,6 +1443,15 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet {
         }
 
     }
+
+    private void forwardToTokenChoiceJsp(final PwmRequest pwmRequest)
+            throws ServletException, PwmUnrecoverableException, IOException
+    {
+        final UserInfo userInfo = ForgottenPasswordUtil.readUserInfo(pwmRequest, forgottenPasswordBean(pwmRequest));
+        final ArrayList<TokenDestinationItem> destItems = new ArrayList<>(TokenDestinationItem.allFromConfig(pwmRequest.getConfig(), userInfo));
+        pwmRequest.setAttribute(PwmRequestAttribute.ForgottenPasswordTokenDestItems, destItems);
+        pwmRequest.forwardToJsp(JspUrl.RECOVER_PASSWORD_TOKEN_CHOICE);
+    }
 }
 
 

+ 10 - 3
src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordUtil.java

@@ -32,6 +32,7 @@ import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.bean.EmailItemBean;
+import password.pwm.bean.TokenDestinationItem;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.Configuration;
 import password.pwm.config.value.data.FormConfiguration;
@@ -397,7 +398,6 @@ class ForgottenPasswordUtil {
                 userIdentity,
                 pwmRequest.getLocale());
 
-        final String tokenDestinationAddress = outputDestrestTokenDataClient.getDisplayValue();
         final Set<String> destinationValues = new LinkedHashSet<>();
         if (outputDestrestTokenDataClient.getEmail() != null) {
             destinationValues.add(outputDestrestTokenDataClient.getEmail());
@@ -417,7 +417,7 @@ class ForgottenPasswordUtil {
 
         final String smsMessage = config.readSettingAsLocalizedString(PwmSetting.SMS_CHALLENGE_TOKEN_TEXT, pwmRequest.getLocale());
 
-        TokenService.TokenSender.sendToken(
+        final List<TokenDestinationItem.Type> sentTypes = TokenService.TokenSender.sendToken(
                 pwmRequest.getPwmApplication(),
                 userInfo,
                 macroMachine,
@@ -430,7 +430,14 @@ class ForgottenPasswordUtil {
         );
 
         StatisticsManager.incrementStat(pwmRequest, Statistic.RECOVERY_TOKENS_SENT);
-        return tokenDestinationAddress;
+
+        final String displayDestAddress = TokenService.TokenSender.figureDisplayString(
+                pwmRequest.getConfig(),
+                sentTypes,
+                outputDestrestTokenDataClient.getEmail(),
+                outputDestrestTokenDataClient.getSms()
+        );
+        return displayDestAddress;
     }
 
 }

+ 5 - 2
src/main/java/password/pwm/http/servlet/newuser/NewUserUtils.java

@@ -63,6 +63,7 @@ import password.pwm.svc.token.TokenPayload;
 import password.pwm.svc.token.TokenService;
 import password.pwm.util.PasswordData;
 import password.pwm.util.RandomPasswordGenerator;
+import password.pwm.util.ValueObfuscator;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.StringUtil;
@@ -498,7 +499,8 @@ class NewUserUtils {
                 }
 
                 newUserBean.getTokenVerificationProgress().getIssuedTokens().add(TokenVerificationProgress.TokenChannel.SMS);
-                newUserBean.getTokenVerificationProgress().setTokenDisplayText(outputDestTokenData.getDisplayValue());
+                final ValueObfuscator valueObfuscator = new ValueObfuscator(pwmApplication.getConfig());
+                newUserBean.getTokenVerificationProgress().setTokenDisplayText(valueObfuscator.maskPhone(toNum));
                 newUserBean.getTokenVerificationProgress().setPhase(TokenVerificationProgress.TokenChannel.SMS);
             }
             break;
@@ -533,7 +535,8 @@ class NewUserUtils {
 
                 newUserBean.getTokenVerificationProgress().getIssuedTokens().add(TokenVerificationProgress.TokenChannel.EMAIL);
                 newUserBean.getTokenVerificationProgress().setPhase(TokenVerificationProgress.TokenChannel.EMAIL);
-                newUserBean.getTokenVerificationProgress().setTokenDisplayText(outputDestTokenData.getDisplayValue());
+                final ValueObfuscator valueObfuscator = new ValueObfuscator(pwmApplication.getConfig());
+                newUserBean.getTokenVerificationProgress().setTokenDisplayText(valueObfuscator.maskEmail(toAddress));
 
                 final EmailItemBean emailItemBean = new EmailItemBean(
                         outputDestTokenData.getEmail(),

+ 57 - 6
src/main/java/password/pwm/svc/token/TokenService.java

@@ -29,8 +29,8 @@ import password.pwm.PwmConstants;
 import password.pwm.bean.EmailItemBean;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.SmsItemBean;
+import password.pwm.bean.TokenDestinationItem;
 import password.pwm.bean.UserIdentity;
-import password.pwm.ldap.UserInfo;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.option.DataStorageMethod;
@@ -46,6 +46,7 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.health.HealthMessage;
 import password.pwm.health.HealthRecord;
 import password.pwm.http.PwmSession;
+import password.pwm.ldap.UserInfo;
 import password.pwm.ldap.auth.SessionAuthenticator;
 import password.pwm.svc.PwmService;
 import password.pwm.svc.event.AuditEvent;
@@ -55,6 +56,7 @@ import password.pwm.svc.intruder.RecordType;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsManager;
 import password.pwm.util.DataStore;
+import password.pwm.util.ValueObfuscator;
 import password.pwm.util.db.DatabaseDataStore;
 import password.pwm.util.db.DatabaseTable;
 import password.pwm.util.java.JavaHelper;
@@ -612,7 +614,7 @@ public class TokenService implements PwmService {
     }
 
     public static class TokenSender {
-        public static void sendToken(
+        public static List<TokenDestinationItem.Type> sendToken(
                 final PwmApplication pwmApplication,
                 final UserInfo userInfo,
                 final MacroMachine macroMachine,
@@ -627,6 +629,8 @@ public class TokenService implements PwmService {
         {
             final boolean success;
 
+            final List<TokenDestinationItem.Type> sentTypes = new ArrayList<>();
+
             try {
                 switch (tokenSendMethod) {
                     case NONE:
@@ -636,27 +640,49 @@ public class TokenService implements PwmService {
                     case BOTH:
                         // Send both email and SMS, success if one of both succeeds
                         final boolean suc1 = sendEmailToken(pwmApplication, userInfo, macroMachine, configuredEmailSetting, emailAddress, tokenKey);
+                        if (suc1) {
+                            sentTypes.add(TokenDestinationItem.Type.email);
+                        }
                         final boolean suc2 = sendSmsToken(pwmApplication, userInfo, macroMachine, smsNumber, smsMessage, tokenKey);
+                        if (suc2) {
+                            sentTypes.add(TokenDestinationItem.Type.sms);
+                        }
                         success = suc1 || suc2;
                         break;
                     case EMAILFIRST:
                         // Send email first, try SMS if email is not available
-                        success = sendEmailToken(pwmApplication, userInfo, macroMachine, configuredEmailSetting, emailAddress, tokenKey) ||
-                                sendSmsToken(pwmApplication, userInfo, macroMachine, smsNumber, smsMessage, tokenKey);
+                        final boolean emailSuccess = sendEmailToken(pwmApplication, userInfo, macroMachine, configuredEmailSetting, emailAddress, tokenKey);
+                        if (emailSuccess) {
+                            success = true;
+                            sentTypes.add(TokenDestinationItem.Type.email);
+                        } else {
+                            success = sendSmsToken(pwmApplication, userInfo, macroMachine, smsNumber, smsMessage, tokenKey);
+                            if (success) {
+                                sentTypes.add(TokenDestinationItem.Type.sms);
+                            }
+                        }
                         break;
                     case SMSFIRST:
                         // Send SMS first, try email if SMS is not available
-                        success = sendSmsToken(pwmApplication, userInfo, macroMachine, smsNumber, smsMessage, tokenKey) ||
-                                sendEmailToken(pwmApplication, userInfo, macroMachine, configuredEmailSetting, emailAddress, tokenKey);
+                        final boolean smsSuccess = sendSmsToken(pwmApplication, userInfo, macroMachine, smsNumber, smsMessage, tokenKey);
+                        if (smsSuccess) {
+                            success = true;
+                            sentTypes.add(TokenDestinationItem.Type.sms);
+                        } else {
+                            success = sendEmailToken(pwmApplication, userInfo, macroMachine, configuredEmailSetting, emailAddress, tokenKey);
+                            sentTypes.add(TokenDestinationItem.Type.email);
+                        }
                         break;
                     case SMSONLY:
                         // Only try SMS
                         success = sendSmsToken(pwmApplication, userInfo, macroMachine, smsNumber, smsMessage, tokenKey);
+                        sentTypes.add(TokenDestinationItem.Type.sms);
                         break;
                     case EMAILONLY:
                     default:
                         // Only try email
                         success = sendEmailToken(pwmApplication, userInfo, macroMachine, configuredEmailSetting, emailAddress, tokenKey);
+                        sentTypes.add(TokenDestinationItem.Type.email);
                         break;
                 }
             } catch (ChaiUnavailableException e) {
@@ -667,6 +693,8 @@ public class TokenService implements PwmService {
                 throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_TOKEN_MISSING_CONTACT));
             }
             pwmApplication.getStatisticsManager().incrementValue(Statistic.TOKENS_SENT);
+
+            return sentTypes;
         }
 
         public static boolean sendEmailToken(
@@ -720,5 +748,28 @@ public class TokenService implements PwmService {
             LOGGER.debug("token SMS added to send queue for " + smsNumber);
             return true;
         }
+
+        public static String figureDisplayString(
+                final Configuration configuration,
+                final List<TokenDestinationItem.Type> sentTypes,
+                final String email,
+                final String sms
+        ) {
+            final ValueObfuscator valueObfuscator = new ValueObfuscator(configuration);
+            final StringBuilder displayDestAddress = new StringBuilder();
+            {
+                if (sentTypes.contains(TokenDestinationItem.Type.email)) {
+                    displayDestAddress.append(valueObfuscator.maskEmail(email));
+                }
+
+                if (sentTypes.contains(TokenDestinationItem.Type.sms)) {
+                    if (displayDestAddress.length() > 0) {
+                        displayDestAddress.append(" & ");
+                    }
+                    displayDestAddress.append(valueObfuscator.maskPhone(sms));
+                }
+            }
+            return displayDestAddress.toString();
+        }
     }
 }

+ 37 - 0
src/main/java/password/pwm/util/ValueObfuscator.java

@@ -0,0 +1,37 @@
+package password.pwm.util;
+
+
+import password.pwm.AppProperty;
+import password.pwm.config.Configuration;
+import password.pwm.util.java.StringUtil;
+
+public class ValueObfuscator {
+    private final Configuration configuration;
+
+    public ValueObfuscator(final Configuration configuration) {
+        this.configuration = configuration;
+    }
+
+    public String maskEmail(final String email) {
+        if (StringUtil.isEmpty(email)) {
+            return "";
+        }
+
+        final String regex = configuration.readAppProperty(AppProperty.TOKEN_MASK_EMAIL_REGEX);
+        final String replace = configuration.readAppProperty(AppProperty.TOKEN_MASK_EMAIL_REPLACE);
+        return email.replaceAll(regex, replace);
+    }
+
+    public String maskPhone(final String phone) {
+        if (StringUtil.isEmpty(phone)) {
+            return "";
+        }
+
+        final String regex = configuration.readAppProperty(AppProperty.TOKEN_MASK_SMS_REGEX);
+        final String replace = configuration.readAppProperty(AppProperty.TOKEN_MASK_SMS_REPLACE);
+        return phone.replaceAll(regex, replace);
+    }
+}
+
+
+

+ 4 - 0
src/main/resources/password/pwm/AppProperty.properties

@@ -268,6 +268,10 @@ telemetry.senderImplementation=password.pwm.svc.telemetry.HttpTelemetrySender
 telemetry.senderSettings={"url":"https://www.pwm-project.org/pwm-data-service/telemetry"}
 telemetry.sendFrequencySeconds=259203
 telemetry.minimumAuthentications=10
+token.mask.email.regex=(?<=.).(?=[^@]*?@)|(?:(?<=@.)|(?!^)\\G(?=[^@]*$)).(?=.*\\.)
+token.mask.email.replace=*
+token.mask.sms.regex=\\d(?!.{0,3}$)
+token.mask.sms.replace=*
 token.maxUniqueCreateAttempts=100
 token.resend.delayMS=3000
 token.verifyPwModifyTime=true

+ 2 - 2
src/main/resources/password/pwm/i18n/Display.properties

@@ -138,8 +138,8 @@ Display_RecoverTokenSendChoiceEmail=Send code to your registered email address.
 Display_RecoverTokenSendChoiceSMS=Send code to your mobile phone using text messaging (SMS).
 Display_RecoverChoiceReset=Set a new password.  If you have forgotten your password and would like to set a new one, click here.  Your account will also be unlocked when you set a new password.
 Display_RecoverChoiceUnlock=Unlock your account.  If you remember your password, you can unlock your account by selecting this option.  Your password will not be changed.
-Display_RecoverEnterCode=To verify your identity, a security code has been sent to you.  Please click the link in the email or copy and paste the security code here.
-Display_RecoverEnterCodeSMS=To verify your identity, a security code has been sent to you by SMS.  Please enter the security code in the message here.
+Display_RecoverEnterCode=To verify your identity, a security code has been sent to you at %1%.  Please click the link in the email or copy and paste the security code here.
+Display_RecoverEnterCodeSMS=To verify your identity, a security code has been sent to your phone at %1%.  Please enter the security code in the message here.
 Display_RecoverPassword=Please answer the following questions. If you answer these questions correctly, you will then be able to reset your password.
 Display_RecoverPasswordChoices=Your account has been locked due to excessive incorrect login attempts.  You may continue by unlocking your account or by changing your password.
 Display_RecoverRandomResponses=You must answer the following questions to continue.

+ 25 - 1
src/main/webapp/WEB-INF/jsp/forgottenpassword-tokenchoice.jsp

@@ -1,4 +1,5 @@
-<%@ page import="password.pwm.http.tag.conditional.PwmIfTest" %>
+<%@ page import="password.pwm.bean.TokenDestinationItem" %>
+<%@ page import="java.util.List" %>
 <%--
   ~ Password Management Servlets (PWM)
   ~ http://www.pwm-project.org
@@ -22,6 +23,7 @@
   --%>
 
 <!DOCTYPE html>
+<% List<TokenDestinationItem> tokenDestinationItems = (List)JspUtility.getAttribute(pageContext, PwmRequestAttribute.ForgottenPasswordTokenDestItems); %>
 <%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %>
 <%@ taglib uri="pwm" prefix="pwm" %>
 <html lang="<pwm:value name="<%=PwmValue.localeCode%>"/>" dir="<pwm:value name="<%=PwmValue.localeDir%>"/>">
@@ -52,6 +54,17 @@
                     <pwm:display key="Display_RecoverTokenSendChoiceEmail"/>
                 </td>
             </tr>
+            <tr>
+                <td>
+                </td>
+                <td>
+                    <% for (final TokenDestinationItem item : tokenDestinationItems) { %>
+                    <% if (item.getType() == TokenDestinationItem.Type.email) { %>
+                    <%=item.getDisplay()%>
+                    <% } %>
+                    <% } %>
+                </td>
+            </tr>
             <tr>
                 <td>
                     &nbsp;
@@ -73,6 +86,17 @@
                     <pwm:display key="Display_RecoverTokenSendChoiceSMS"/>
                 </td>
             </tr>
+            <tr>
+                <td>
+                </td>
+                <td>
+                    <% for (final TokenDestinationItem item : tokenDestinationItems) { %>
+                    <% if (item.getType() == TokenDestinationItem.Type.sms) { %>
+                    <%=item.getDisplay()%>
+                    <% } %>
+                    <% } %>
+                </td>
+            </tr>
             <tr>
                 <td>
                     &nbsp;