1

I'm an admin and beginner developer who was setting up a community and thought that the login discovery page was a great user experience. Without due-diligence, I went ahead and selected the "create a login discovery handler" as well as the "create a configurable self-reg handler". The desired result is a great login experience, but quite frankly, I was a little surprised that Salesforce auto-generated an apex class at the click of a button, without also generating a corresponding test class. Now my test coverage is blown and I need help creating test classes for the two. I haven't configured anything from the code and what you see if exactly what Salesforce auto-generated. Any help would be great appreciated!

Here is the first

global class AutocreatedConfigSelfReg123456789 implements Auth.ConfigurableSelfRegHandler {

  private final Long CURRENT_TIME = Datetime.now().getTime();
  private final String[] UPPERCASE_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
  private final String[] LOWERCASE_CHARS = 'abcdefghijklmnopqrstuvwxyz'.split('');
  private final String[] NUMBER_CHARS = '1234567890'.split('');
  private final String[] SPECIAL_CHARS = '!#$%-_=+<>'.split('');

  // This method is called once after verification (if any was configured)
  // This method should create a user and insert it
  // Password can be null
  // Return null or throw an exception to fail creation
  global Id createUser(Id accountId, Id profileId, Map<SObjectField, String> registrationAttributes, String password) {
    User u = new User();
    u.ProfileId = profileId;
    for (SObjectField field : registrationAttributes.keySet()) {
      String value = registrationAttributes.get(field);
      u.put(field, value);
    }

    u = handleUnsetRequiredFields(u);
    generateContact(u, accountId);
    if (String.isBlank(password)) {
      password = generateRandomPassword();
    }
    Site.validatePassword(u, password, password);
    if (u.contactId == null) {
      return Site.createExternalUser(u, accountId, password);
    }
    u.languagelocalekey = UserInfo.getLocale();
    u.localesidkey = UserInfo.getLocale();
    u.emailEncodingKey = 'UTF-8';
    u.timeZoneSidKey = UserInfo.getTimezone().getID();
    insert u;
    System.setPassword(u.Id, password);
    return u.id;
  }

  // Method to autogenerate a password if one was not passed in
  // By setting a password for a user, we won't send a welcome email to set the password
  private String generateRandomPassword() {
    String[] characters = new List<String>(UPPERCASE_CHARS);
    characters.addAll(LOWERCASE_CHARS);
    characters.addAll(NUMBER_CHARS);
    characters.addAll(SPECIAL_CHARS);
    String newPassword = '';
    Boolean needsUpper = true, needsLower = true, needsNumber = true, needsSpecial = true;
    while (newPassword.length() < 50) {
      Integer randomInt = generateRandomInt(characters.size());
      String c = characters[randomInt];
      if (needsUpper && c.isAllUpperCase()) {
        needsUpper = false;
      } else if (needsLower && c.isAllLowerCase()) {
        needsLower = false;
      } else if (needsNumber && c.isNumeric()) {
        needsNumber = false;
      } else if (needsSpecial && !c.isAlphanumeric()) {
        needsSpecial = false;
      }
      newPassword += c; 
    }
    newPassword = addMissingPasswordRequirements(newPassword, needsLower, needsUpper, needsNumber, needsSpecial);
    return newPassword;
  }

  private String addMissingPasswordRequirements(String password, Boolean addLowerCase, Boolean addUpperCase, Boolean addNumber, Boolean addSpecial) {
    if (addLowerCase) {
      password += LOWERCASE_CHARS[generateRandomInt(LOWERCASE_CHARS.size())];
    }
    if (addUpperCase) {
      password += UPPERCASE_CHARS[generateRandomInt(UPPERCASE_CHARS.size())];
    }
    if (addNumber) {
      password += NUMBER_CHARS[generateRandomInt(NUMBER_CHARS.size())];
    }
    if (addSpecial) {
      password += SPECIAL_CHARS[generateRandomInt(SPECIAL_CHARS.size())];
    }
    return password;
  }

  // Generates a random number from 0 up to, but not including, max.
  private Integer generateRandomInt(Integer max) {
    return Math.mod(Math.abs(Crypto.getRandomInteger()), max);
  }

  // Loops over required fields that were not passed in to set to some default value
  private User handleUnsetRequiredFields(User u) {
    if (String.isBlank(u.LastName)){
      u.LastName = generateLastName();
    }
    if (String.isBlank(u.Username)) {
      u.Username = generateUsername();
    }
    if (String.isBlank(u.Email)) {
      u.Email = generateEmail();
    }
    if (String.isBlank(u.Alias)) {
      u.Alias = generateAlias();
    }
    if (String.isBlank(u.CommunityNickname)) {
      u.CommunityNickname = generateCommunityNickname();
    }
    return u;
  }

  // Method to construct a contact for a user
  private void generateContact(User u, Id accountId) {
    // Add logic here if you want to build your own contact for the user
  }

  // Default implementation to try to provide uniqueness
  private String generateAlias() {
    String timeString = String.valueOf(CURRENT_TIME);
    return timeString.substring(timeString.length() - 8);
  }

  // Default implementation to try to provide uniqueness
  private String generateLastName() {
    return 'ExternalUser' + CURRENT_TIME;
  }

  // Default implementation to try to provide uniqueness
  private String generateUsername() {
    return 'externaluser' + CURRENT_TIME + '@company.com';
  }

  // Default implementation to try to provide uniqueness
  private String generateEmail() {
    return 'externaluser' + CURRENT_TIME + '@company.com';
  }

  // Default implementation to try to provide uniqueness
  private String generateCommunityNickname() {
    return 'ExternalUser' + CURRENT_TIME;
  }
}
  1. And here is the other
global class AutocreatedDiscLoginHandler123456789 implements Auth.LoginDiscoveryHandler {

global PageReference login(String identifier, String startUrl, Map<String, String> requestAttributes) {
  if (identifier != null && isValidEmail(identifier)) {
    // Search for user by email 
    List<User> users = [SELECT Id FROM User WHERE Email = :identifier AND IsActive = TRUE];
    if (!users.isEmpty() && users.size() == 1) {
      // User must have verified email before using this verification method. We cannot send messages to unverified emails. 
      // You can check if the user has email verified bit on and add the password verification method as fallback.
      List<TwoFactorMethodsInfo> verifiedInfo = [SELECT HasUserVerifiedEmailAddress FROM TwoFactorMethodsInfo WHERE UserId = :users[0].Id];
      if (!verifiedInfo.isEmpty() && verifiedInfo[0].HasUserVerifiedEmailAddress == true) {
        // Use email verification method if the user's email is verified.
        return discoveryResult(users[0], Auth.VerificationMethod.EMAIL, startUrl, requestAttributes);
      } else {
        // Use password verification method as fallback if the user's email is unverified.
        return discoveryResult(users[0], Auth.VerificationMethod.PASSWORD, startUrl, requestAttributes);
      }
    } else {
      throw new Auth.LoginDiscoveryException('No unique user found. User count=' + users.size());
    }
  }
  if (identifier != null) {
    String formattedSms = getFormattedSms(identifier);
    if (formattedSms != null) {
      // Search for user by SMS 
      List<User> users = [SELECT Id FROM User WHERE MobilePhone = :formattedSms AND IsActive = TRUE];
      if (!users.isEmpty() && users.size() == 1) {
        // User must have verified SMS before using this verification method. We cannot send messages to unverified mobile numbers. 
        // You can check if the user has mobile verified bit on or add the password verification method as fallback.
        List<TwoFactorMethodsInfo> verifiedInfo = [SELECT HasUserVerifiedMobileNumber FROM TwoFactorMethodsInfo WHERE UserId = :users[0].Id];
        if (!verifiedInfo.isEmpty() && verifiedInfo[0].HasUserVerifiedMobileNumber == true) {
          // Use SMS verification method if the user's mobile number is verified.
          return discoveryResult(users[0], Auth.VerificationMethod.SMS, startUrl, requestAttributes);
        } else {
          // Use password verification method as fallback if the user's mobile number is unverified.
          return discoveryResult(users[0], Auth.VerificationMethod.PASSWORD, startUrl, requestAttributes);
        }
      } else {
        throw new Auth.LoginDiscoveryException('No unique user found. User count=' + users.size());
      }
    }
  }
  if (identifier != null) {
    // You can customize the code to find user via other attributes, such as SSN or Federation ID
  }
  throw new Auth.LoginDiscoveryException('Invalid Identifier');
}

private boolean isValidEmail(String identifier) {
  String emailRegex = '^[a-zA-Z0-9._|\\\\%#~`=?&/$^*!}{+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,4}$';
  // source: http://www.regular-expressions.info/email.html 
  Pattern EmailPattern = Pattern.compile(emailRegex);
  Matcher EmailMatcher = EmailPattern.matcher(identifier);
  if (EmailMatcher.matches()) { return true; }
  else { return false; }
}

private String getFormattedSms(String identifier) {
  // Accept SMS input formats with 1 or 2 digits country code, 3 digits area code and 7 digits number
  // You can customize the SMS regex to allow different formats
  String smsRegex = '^(\\+?\\d{1,2}?[\\s-])?(\\(?\\d{3}\\)?[\\s-]?\\d{3}[\\s-]?\\d{4})$';
  Pattern smsPattern = Pattern.compile(smsRegex);
  Matcher smsMatcher = SmsPattern.matcher(identifier);
  if (smsMatcher.matches()) {
    try {
      // Format user input into the verified SMS format '+xx xxxxxxxxxx' before DB lookup
      // Append US country code +1 by default if no country code is provided
      String countryCode = smsMatcher.group(1) == null ? '+1' : smsMatcher.group(1);
      return System.UserManagement.formatPhoneNumber(countryCode, smsMatcher.group(2));
    } catch(System.InvalidParameterValueException e) {
      return null;
    }
  } else { return null; }
}

private PageReference getSsoRedirect(User user, String startUrl, Map<String, String> requestAttributes) {
  // You can look up if the user should log in with SAML or an Auth Provider and return the URL to initialize SSO.
  return null;
}

private PageReference discoveryResult(User user, Auth.VerificationMethod method, String startUrl, Map<String, String> requestAttributes) {
  //Only external users with an External Identity or community license can login using Site.passwordlessLogin
  //Use getSsoRedirect to enable internal user login for a community
  PageReference ssoRedirect = getSsoRedirect(user, startUrl, requestAttributes);
  if (ssoRedirect != null) {
    return ssoRedirect;
  } else {
    if (method != null) {
      List<Auth.VerificationMethod> methods = new List<Auth.VerificationMethod>();
      methods.add(method);
      PageReference pwdlessRedirect = Site.passwordlessLogin(user.Id, methods, startUrl);
      if (pwdlessRedirect != null) {
        return pwdlessRedirect;
      } else {
        throw new Auth.LoginDiscoveryException('No Passwordless Login redirect URL returned for verification method: ' + method);
      }
    } else {
      throw new Auth.LoginDiscoveryException('No method found');
    }
  }
}
}
  • 1
    Salesforce does this for a number of features. Salesforce can't do this for you since it doesn't know how you might decide to further customize or extend the base code it has provided. You should find unit tests associated with these classes that you'll need to add additional code to in order to obtain full coverage. If you try Search (upper RH corner) you'll find salesforce.stackexchange.com/questions/222912/… and other answers related to your question. – crmprogdev Nov 29 '19 at 13:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.