Changeset 7410


Ignore:
Timestamp:
Oct 9, 2017, 1:47:39 PM (5 years ago)
Author:
Nicklas Nordborg
Message:

References #2097: Implement support for device verification

Added support for verifying email addresses. The "BASE->Contact information" dialog now has a "2-factor login" checkbox. If this checkbox is checked or if the email address is changed an email is sent to the user with a verification code. At the same time, the browser displays a new form asking the user to enter the verfication code. The 2-factor login is only enabled if the correct verification code is entered.

The email message template is currently hard-coded but it should may be configurable?

Location:
trunk
Files:
2 added
8 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/core/net/sf/basedb/core/SessionControl.java

    r7409 r7410  
    493493        Email email = EmailUtil.createSimpleEmail();
    494494        email.addTo(user.getEmail(), user.getName(), "UTF-8");
    495         email.setSubject("Your verification code for " + serverUrl);
     495        email.setSubject("Your login verification code for " + serverUrl);
    496496        email.setMsg(msg);
    497497        email.send();
  • trunk/src/core/net/sf/basedb/core/User.java

    r7404 r7410  
    3434import net.sf.basedb.core.data.DirectoryData;
    3535import net.sf.basedb.core.hibernate.TypeWrapper;
     36import net.sf.basedb.util.EmailUtil;
    3637import net.sf.basedb.util.EqualsHelper;
    3738import net.sf.basedb.util.MD5;
     
    5152import java.util.List;
    5253import java.util.Set;
     54
     55import org.apache.commons.mail.Email;
     56import org.apache.commons.mail.EmailException;
     57
    5358import java.util.Collections;
    5459
     
    594599    enable this feature by itself, the email address must first
    595600    be verified.
    596     @see xxxx
     601    @see #sendEmailVerificationCode()
     602    @see #enableDeviceVerification(String)
    597603    @since 3.12
    598604  */
     
    602608    if (getData().getEmail() == null) useDeviceVerification = false;
    603609    getData().setUseDeviceVerification(useDeviceVerification);
     610  }
     611 
     612  private String verificationCode;
     613  private String verifiedEmail;
     614 
     615  /**
     616    Send a verification code to the currently registered email address.
     617    The client application should ask the user to enter the verification
     618    code and then call {@link #verifyEmail(String)}. The verification must
     619    be done with the same User instance since the verification code
     620    is only temporarily stored inside this instance.
     621    @param serverUrl URL to the server or null to use the default application title
     622    @since 3.12
     623  */
     624  public void sendEmailVerificationCode(String serverUrl)
     625  {
     626    checkPermission(Permission.RESTRICTED_WRITE);
     627   
     628    // No email = no device verification
     629    if (!EmailUtil.isEnabled()) return;
     630    if (!EmailUtil.isValidEmail(getEmail())) return;
     631
     632    String code = MD5.leftPad(Integer.toString((int)(Math.random()*1000000)), '0', 6);
     633   
     634    try
     635    {
     636      SessionControl sc = getSessionControl();
     637      if (serverUrl == null) serverUrl = Application.getTitle();
     638     
     639      String msg = "Hi " + getName() + ",\n\n" +
     640        "The verification code is: " + code + "\n\n"
     641        + "This email was sent to you because you are trying to enable\n"
     642        + "2-factor login to " + serverUrl + ".\n"
     643        + "Please enter the verification code in the form on your\n"
     644        + "browser to verify that your email address is correct.\n"
     645        + "The verification code can only be used once.\n\n"
     646        + "Please contact the server administrator if you have any questions.\n\n"
     647        + "The request was made from: " + sc.getRemoteId() + "\n";
     648     
     649      Email email = EmailUtil.createSimpleEmail();
     650      email.addTo(getEmail(), getName(), "UTF-8");
     651      email.setSubject("Your email verification code for " + serverUrl);
     652      email.setMsg(msg);
     653      email.send();
     654    }
     655    catch (EmailException ex)
     656    {
     657      throw new LoginException("Could not send verification code", ex);
     658    }
     659   
     660    verificationCode = code;
     661    verifiedEmail = getEmail();
     662  }
     663 
     664  /**
     665    Verify the email and enables device verification.
     666    @param code The code that was sent by email to the user
     667    @see #sendEmailVerificationCode()
     668    @since 3.12
     669  */
     670  public void enableDeviceVerification(String code)
     671  {
     672    checkPermission(Permission.RESTRICTED_WRITE);
     673    if (verificationCode == null)
     674    {
     675      throw new IllegalStateException("No email is waiting for verification.");
     676    }
     677
     678    // Check that the email hasn't changed since the verification code was sent!
     679    if (!verifiedEmail.equals(getEmail()))
     680    {
     681      throw new InvalidDataException("The email address was modified after the code was sent.");
     682    }
     683   
     684    // Check the verification code!
     685    if (!verificationCode.equals(code))
     686    {
     687      // Not correct.
     688      throw new InvalidDataException("The verification code was not correct.");
     689    }
     690    verificationCode = null;
     691    verifiedEmail = null;
     692    getData().setUseDeviceVerification(true);
     693  }
     694 
     695  /**
     696    Disable device verification.
     697    @since 3.12
     698  */
     699  public void disableDeviceVerification()
     700  {
     701    checkPermission(Permission.RESTRICTED_WRITE);
     702    getData().setUseDeviceVerification(false);
    604703  }
    605704 
  • trunk/www/admin/users/edit_user.jsp

    r7404 r7410  
    459459      </tr>
    460460      <tr>
    461         <th><label for="use_device_verification">Device verfication</label></th>
     461        <th><label for="use_device_verification">2-factor login</label></th>
    462462        <td><input type="checkbox" name="use_device_verification" id="use_device_verification" value="1"
    463463          <%=(user != null && user.getUseDeviceVerification()) ||
  • trunk/www/admin/users/list_users.jsp

    r7404 r7410  
    242242        property="useDeviceVerification"
    243243        datatype="boolean"
    244         title="Device verification"
     244        title="2-factor login"
    245245        sortable="true"
    246246        filterable="true"
  • trunk/www/admin/users/view_user.jsp

    r7404 r7410  
    247247        </tr>
    248248        <tr>
    249           <th>Device verification</th>
     249          <th>2-factor login</th>
    250250          <td><%=user.getUseDeviceVerification() ? "yes" : "no"%>
    251251          <%
  • trunk/www/login.jsp

    r7409 r7410  
    7171      LoginRequest loginRequest = new LoginRequest(login, password, deviceToken);
    7272      loginRequest.setAttribute("user-agent", request.getHeader("User-Agent"));
    73       String serverUrl = request.getRequestURL().toString().replace("/login.jsp", "/");
     73      String serverUrl = request.getRequestURL().toString().replace(request.getRequestURI(), root);
    7474      loginRequest.setAttribute("server-url", serverUrl);
    7575      sc.login(loginRequest);
  • trunk/www/my_base/user/settings.jsp

    r6428 r7410  
    140140            <td></td>
    141141          </tr>
     142          <tr>
     143            <th class="subprompt"></th>
     144            <td>
     145              <%
     146              boolean useDeviceVerification = user.getUseDeviceVerification();
     147              %>
     148              <input type="checkbox" name="useDeviceVerification" id="useDeviceVerification" value="1" <%=useDeviceVerification ? "checked" : ""%>
     149                ><label for="useDeviceVerification">Enable 2-factor login</label>
     150            </td>
     151            <td></td>
     152          </tr>
    142153          <%
    143154        }
  • trunk/www/my_base/user/submit_user.jsp

    r7295 r7410  
    5959String root = request.getContextPath()+"/";
    6060
     61String forward = null;
    6162DbControl dc = sc.newDbControl();
    6263try
     
    7778    // Contact information tab
    7879    user.setEmail(email);
     80    boolean useDeviceVerification = Values.getBoolean(request.getParameter("useDeviceVerification"));
     81    if (!useDeviceVerification) user.disableDeviceVerification();
    7982    if (EmailUtil.isEnabled())
    8083    {
    8184      user.setSendMessagesAsEmail(email != null && Values.getBoolean(request.getParameter("sendMessagesAsEmail")));
     85      if (useDeviceVerification && !user.getUseDeviceVerification())
     86      {
     87        // Send a verification code for verifying the email address
     88        forward = "verify_email.jsp?ID="+ID;
     89      }
    8290    }
    8391    user.setOrganisation(Values.getStringOrNull(request.getParameter("organisation")));
     
    111119    sc.setSessionSetting("user", null);
    112120    message = "Information saved";
     121  }
     122  else if ("VerifyEmail".equals(cmd))
     123  {
     124    String verificationCode = request.getParameter("verificationCode");
     125    User user = (User)sc.getSessionSetting("user");
     126    dc.reattachItem(user, false);
     127    user.enableDeviceVerification(verificationCode);
     128    message = "The email has been verified";
     129    dc.commit();
    113130  }
    114131  else if ("SavePreferences".equals(cmd))
     
    204221  if (dc != null) dc.close();
    205222}
    206 response.sendRedirect(root + "common/close_popup.jsp?ID="+ID+"&refresh_opener=1&&message="+HTML.urlEncode(message));
     223
     224if (forward != null)
     225{
     226  pageContext.forward(forward);
     227}
     228else
     229{
     230  response.sendRedirect(root + "common/close_popup.jsp?ID="+ID+"&refresh_opener=1&&message="+HTML.urlEncode(message));
     231}
    207232%>
    208233
Note: See TracChangeset for help on using the changeset viewer.