diff --git a/src/main/java/org/wise/portal/dao/authentication/TeacherUserDetailsDao.java b/src/main/java/org/wise/portal/dao/authentication/TeacherUserDetailsDao.java new file mode 100644 index 0000000000..7c00f8fe80 --- /dev/null +++ b/src/main/java/org/wise/portal/dao/authentication/TeacherUserDetailsDao.java @@ -0,0 +1,9 @@ +package org.wise.portal.dao.authentication; + +import org.wise.portal.dao.SimpleDao; +import org.wise.portal.domain.authentication.impl.TeacherUserDetails; + +public interface TeacherUserDetailsDao extends SimpleDao { + + boolean hasVerificationCode(String verificationCode); +} diff --git a/src/main/java/org/wise/portal/dao/authentication/impl/HibernateTeacherUserDetailsDao.java b/src/main/java/org/wise/portal/dao/authentication/impl/HibernateTeacherUserDetailsDao.java new file mode 100644 index 0000000000..2891af7d2f --- /dev/null +++ b/src/main/java/org/wise/portal/dao/authentication/impl/HibernateTeacherUserDetailsDao.java @@ -0,0 +1,33 @@ +package org.wise.portal.dao.authentication.impl; + +import javax.persistence.TypedQuery; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Root; + +import org.springframework.stereotype.Repository; +import org.wise.portal.dao.authentication.TeacherUserDetailsDao; +import org.wise.portal.dao.impl.AbstractHibernateDao; +import org.wise.portal.domain.authentication.impl.TeacherUserDetails; + +@Repository +public class HibernateTeacherUserDetailsDao extends AbstractHibernateDao + implements TeacherUserDetailsDao { + + @Override + public boolean hasVerificationCode(String verificationCode) { + CriteriaBuilder cb = getCriteriaBuilder(); + CriteriaQuery cq = cb.createQuery(Long.class); + Root teacherUserDetailsRoot = cq.from(TeacherUserDetails.class); + cq.select(cb.count(teacherUserDetailsRoot)) + .where(cb.equal(teacherUserDetailsRoot.get("verificationCode"), verificationCode)); + TypedQuery query = entityManager.createQuery(cq); + Long count = query.getSingleResult(); + return count > 0; + } + + @Override + protected Class getDataObjectClass() { + return TeacherUserDetails.class; + } +} diff --git a/src/main/java/org/wise/portal/dao/user/UserDao.java b/src/main/java/org/wise/portal/dao/user/UserDao.java index b0d8715c3b..fbc10f77d3 100644 --- a/src/main/java/org/wise/portal/dao/user/UserDao.java +++ b/src/main/java/org/wise/portal/dao/user/UserDao.java @@ -46,6 +46,7 @@ List retrieveStudentsByNameAndBirthday(String firstName, String lastName, List retrieveTeachersByFirstName(String firstName); List retrieveTeachersByLastName(String lastName); User retrieveTeacherByUsername(String username); + User retrieveTeacherByVerificationCode(String verificationCode); List retrieveTeachersByDisplayName(String displayName); List retrieveTeachersByCity(String city); List retrieveTeachersByState(String state); diff --git a/src/main/java/org/wise/portal/dao/user/impl/HibernateUserDao.java b/src/main/java/org/wise/portal/dao/user/impl/HibernateUserDao.java index 6a97b31d53..8a8f64f0de 100644 --- a/src/main/java/org/wise/portal/dao/user/impl/HibernateUserDao.java +++ b/src/main/java/org/wise/portal/dao/user/impl/HibernateUserDao.java @@ -148,8 +148,7 @@ public List retrieveTeachersByLastName(String lastName) { } public User retrieveTeacherByUsername(String username) { - List resultList = retrieveTeachersByFieldValue("username", username); - return resultList.isEmpty() ? null : resultList.get(0); + return retrieveTeacherByFieldValue("username", username); } public List retrieveTeachersByDisplayName(String displayName) { @@ -176,6 +175,15 @@ public List retrieveTeachersByEmail(String emailAddress) { return retrieveTeachersByFieldValue("emailAddress", emailAddress); } + public User retrieveTeacherByVerificationCode(String verificationCode) { + return retrieveTeacherByFieldValue("verificationCode", verificationCode); + } + + private User retrieveTeacherByFieldValue(String field, String value) { + List resultList = retrieveTeachersByFieldValue(field, value); + return resultList.isEmpty() ? null : resultList.get(0); + } + @SuppressWarnings("unchecked") private List retrieveTeachersByFieldValue(String field, String value) { CriteriaBuilder cb = getCriteriaBuilder(); diff --git a/src/main/java/org/wise/portal/domain/authentication/impl/TeacherUserDetails.java b/src/main/java/org/wise/portal/domain/authentication/impl/TeacherUserDetails.java index 76f7e51340..b6cb045b75 100644 --- a/src/main/java/org/wise/portal/domain/authentication/impl/TeacherUserDetails.java +++ b/src/main/java/org/wise/portal/domain/authentication/impl/TeacherUserDetails.java @@ -25,6 +25,7 @@ import java.util.Date; import java.util.HashMap; +import java.util.UUID; import javax.persistence.Column; import javax.persistence.Entity; @@ -93,6 +94,12 @@ public class TeacherUserDetails extends PersistentUserDetails implements Mutable @Transient private static final String COLUMN_NAME_HOW_HEAR = "howDidYouHearAboutUs"; + @Transient + private static final String COLUMN_NAME_VERIFIED = "isVerified"; + + @Transient + private static final String COLUMN_NAME_VERIFICATION_CODE = "verificationCode"; + @Transient private static final long serialVersionUID = 1L; @@ -164,6 +171,15 @@ public class TeacherUserDetails extends PersistentUserDetails implements Mutable @Setter private String howDidYouHearAboutUs; + @Column(name = TeacherUserDetails.COLUMN_NAME_VERIFIED, nullable = false) + @Getter + @Setter + private boolean verified = false; + + @Column(name = TeacherUserDetails.COLUMN_NAME_VERIFICATION_CODE, unique = true) + @Getter + private String verificationCode; + public String getCoreUsername() { return (firstname + lastname).replaceAll("[\\s-]+", ""); } @@ -183,9 +199,7 @@ public String[] getUsernameSuffixes() { */ public String getNextUsernameSuffix(String currentUsernameSuffix) { String nextUsernameSuffix = ""; - if (currentUsernameSuffix == null) { - nextUsernameSuffix = ""; - } else if ("".equals(currentUsernameSuffix)) { + if ("".equals(currentUsernameSuffix)) { nextUsernameSuffix = "1"; } else { try { @@ -238,4 +252,8 @@ public boolean isEmailValid() { public void setEmailValid(boolean emailValid) { this.emailValid = emailValid; } + + public void setVerificationCode() { + this.verificationCode = UUID.randomUUID().toString(); + } } diff --git a/src/main/java/org/wise/portal/presentation/web/TeacherVerificationAPIController.java b/src/main/java/org/wise/portal/presentation/web/TeacherVerificationAPIController.java new file mode 100644 index 0000000000..12d4ec8de6 --- /dev/null +++ b/src/main/java/org/wise/portal/presentation/web/TeacherVerificationAPIController.java @@ -0,0 +1,67 @@ +package org.wise.portal.presentation.web; + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.security.access.annotation.Secured; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.wise.portal.domain.authentication.impl.TeacherUserDetails; +import org.wise.portal.domain.user.User; +import org.wise.portal.presentation.web.controllers.teacher.TeacherAPIController; + +@RestController +@RequestMapping("/api/teacher/verify") +public class TeacherVerificationAPIController extends TeacherAPIController { + + @GetMapping() + @Secured({ "ROLE_ANONYMOUS" }) + public void verifyTeacherAndRedirect(@RequestParam String code, HttpServletResponse response, + HttpServletRequest request) throws IOException { + User user = userService.retrieveTeacherByVerificationCode(code); + boolean verified = verifyTeacher(user); + String link = getLoginLink(user, verified); + sendWelcomeEmail(user, link, request); + response.sendRedirect(link); + } + + private boolean verifyTeacher(User user) { + if (user != null) { + TeacherUserDetails tud = (TeacherUserDetails) user.getUserDetails(); + return verifyTeacherAccount(user, tud); + } else { + return false; + } + } + + private String getLoginLink(User user, boolean verified) { + StringBuilder link = new StringBuilder("/login?verified="); + link.append(user == null ? "error" : verified); + if (user != null) { + link.append("&username=").append(user.getUserDetails().getUsername()); + } + return link.toString(); + } + + private void sendWelcomeEmail(User user, String link, HttpServletRequest request) { + if (link.contains("verified=true")) { + TeacherUserDetails tud = (TeacherUserDetails) user.getUserDetails(); + this.teacherMailService.sendWelcomeEmail(tud.getEmailAddress(), tud.getDisplayname(), tud.getUsername(), + false, request.getLocale(), request); + } + } + + private boolean verifyTeacherAccount(User user, TeacherUserDetails tud) { + if (!tud.isVerified()) { + tud.setVerified(true); + userService.updateUser(user); + return true; + } else { + return false; + } + } +} diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java index 92e6baadd5..d4534e1049 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java @@ -30,6 +30,7 @@ import org.wise.portal.presentation.web.controllers.user.UserAPIController; import org.wise.portal.presentation.web.response.SimpleResponse; import org.wise.portal.service.authentication.UserDetailsService; +import org.wise.portal.service.mail.teacher.TeacherMailService; import org.wise.portal.service.usertags.UserTagsService; /** @@ -44,6 +45,9 @@ @Secured({ "ROLE_TEACHER" }) public class TeacherAPIController extends UserAPIController { + @Autowired + protected TeacherMailService teacherMailService; + @Autowired private UserDetailsService userDetailsService; @@ -326,4 +330,8 @@ HashMap editRunIsLockedAfterEndDate(Authentication authenticatio } return response; } + + protected boolean isTeacher(User user) { + return user != null && !user.getRoles().contains("ROLE_STUDENT"); + } } diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java index cc18cd734e..7047ec1402 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIController.java @@ -3,7 +3,6 @@ import java.util.Locale; import java.util.Map; -import javax.mail.MessagingException; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.RandomStringUtils; @@ -26,12 +25,6 @@ @RestController @RequestMapping("/api/teacher/register") public class TeacherRegistrationAPIController extends TeacherAPIController { - - private final String emailCodePrefix = - "presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmail"; - private final String welcomeBodyCode = this.emailCodePrefix + "Body"; - private final String welcomeSocialAccountBodyCode = this.emailCodePrefix + "BodyNoUsername"; - private final String welcomeSubjectCode = this.emailCodePrefix + "Subject"; @PostMapping() @Secured({ "ROLE_ANONYMOUS" }) @@ -44,67 +37,33 @@ ResponseEntity> createTeacherAccount( return ResponseEntityGenerator.createError("recaptchaResponseInvalid"); } catch (InvalidPasswordException e) { return ResponseEntityGenerator.createError( - passwordService.getErrors(teacherFields.get("password"))); + passwordService.getErrors(teacherFields.get("password"))); } Locale locale = request.getLocale(); - TeacherUserDetails tud = createTeacherUserDetails(teacherFields, locale); + boolean isSocialAccount = isSocialAccount(teacherFields); + TeacherUserDetails tud = createTeacherUserDetails(teacherFields, isSocialAccount, locale); User createdUser = this.userService.createUser(tud); String username = createdUser.getUserDetails().getUsername(); - if (isSendEmailEnabled()) { - sendWelcomeTeacherEmail(tud.getEmailAddress(), tud.getDisplayname(), username, - isSocialAccount(tud), locale, request); - } + sendNewTeacherEmail(request, locale, isSocialAccount, tud, username); return createRegisterSuccessResponse(username); } - private boolean isSendEmailEnabled() { - String sendEmailEnabledStr = appProperties.getProperty("send_email_enabled", "false"); - return Boolean.valueOf(sendEmailEnabledStr); - } - - private boolean isSocialAccount(TeacherUserDetails tud) { - return isSet(tud.getGoogleUserId()) || isSet(tud.getMicrosoftUserId()); - } - - private void sendWelcomeTeacherEmail(String email, String displayName, String username, - boolean socialAccount, Locale locale, - HttpServletRequest request) { - String subject = getEmailMessage(this.welcomeSubjectCode, this.welcomeSubjectCode, null, locale); - String body = getWelcomeTeacherBody(displayName, username, socialAccount, locale, request); - this.sendEmail(email, subject, body); - } - - private String getEmailMessage(String defaultCode, String code, Object[] args, Locale locale) { - String defaultMessage = messageSource.getMessage(defaultCode, args, Locale.US); - return messageSource.getMessage(code, args, defaultMessage, locale); - } - - private String getWelcomeTeacherBody(String displayName, String username, boolean socialAccount, - Locale locale, HttpServletRequest request) { - String gettingStartedUrl = getGettingStartedUrl(request); - String code = socialAccount ? this.welcomeSocialAccountBodyCode : this.welcomeBodyCode; - Object[] args = socialAccount - ? new Object[] { displayName, gettingStartedUrl } - : new Object[] { displayName, username, gettingStartedUrl }; - return getEmailMessage(this.welcomeBodyCode, code, args, locale); - } - - private String getGettingStartedUrl(HttpServletRequest request) { - return ControllerUtil.getPortalUrlString(request) + "/help/getting-started"; + private void sendNewTeacherEmail(HttpServletRequest request, Locale locale, boolean isSocialAccount, + TeacherUserDetails tud, String username) { + if (isSocialAccount) { + this.teacherMailService.sendWelcomeEmail(tud.getEmailAddress(), tud.getDisplayname(), username, + true, locale, request); + } else { + this.teacherMailService.sendVerifyEmail(tud.getEmailAddress(), tud.getVerificationCode(), locale, request); + } } - private void sendEmail(String email, String subject, String body) { - String fromEmail = appProperties.getProperty("portalemailaddress"); - String[] recipients = { email }; - try { - mailService.postMail(recipients, subject, body, fromEmail); - } catch (MessagingException e) { - e.printStackTrace(); - } + private boolean isSocialAccount(Map teacherFields) { + return isSet(teacherFields.get("googleUserId")) || isSet(teacherFields.get("microsoftUserId")); } private void validateTeacherFields(Map teacherFields) - throws RecaptchaVerificationException, InvalidNameException, InvalidPasswordException { + throws RecaptchaVerificationException, InvalidNameException, InvalidPasswordException { validateReCaptcha(teacherFields.get("token")); validateFirstAndLastName(teacherFields.get("firstName"), teacherFields.get("lastName")); validatePassword(teacherFields.get("password")); @@ -119,7 +78,7 @@ private void validateReCaptcha(String token) throws RecaptchaVerificationExcepti } private void validateFirstAndLastName(String firstName, String lastName) - throws InvalidNameException { + throws InvalidNameException { if (!isFirstNameAndLastNameValid(firstName, lastName)) { String messageCode = this.getInvalidNameMessageCode(firstName, lastName); throw new InvalidNameException(messageCode); @@ -133,7 +92,7 @@ private void validatePassword(String password) throws InvalidPasswordException { } private TeacherUserDetails createTeacherUserDetails(Map teacherFields, - Locale locale) { + boolean isSocialAccount, Locale locale) { TeacherUserDetails tud = new TeacherUserDetails(); tud.setFirstname(teacherFields.get("firstName")); tud.setLastname(teacherFields.get("lastName")); @@ -148,6 +107,8 @@ private TeacherUserDetails createTeacherUserDetails(Map teacherF tud.setLanguage(locale.getLanguage()); setPassword(teacherFields, tud); tud.setEmailValid(true); + tud.setVerified(isSocialAccount || !teacherMailService.isSendEmailEnabled()); + tud.setVerificationCode(); return tud; } diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIController.java new file mode 100644 index 0000000000..38e5c02501 --- /dev/null +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIController.java @@ -0,0 +1,39 @@ +package org.wise.portal.presentation.web.controllers.teacher; + +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.annotation.Secured; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.wise.portal.domain.authentication.impl.TeacherUserDetails; +import org.wise.portal.domain.user.User; +import org.wise.portal.presentation.web.response.ResponseEntityGenerator; + +@RestController +@RequestMapping("/api/teacher/send-verify-email") +public class TeacherVerificationEmailAPIController extends TeacherAPIController { + + @PostMapping() + @Secured({ "ROLE_ANONYMOUS" }) + ResponseEntity> sendVerificationEmail(@RequestParam String username, + HttpServletRequest request) { + User user = userService.retrieveTeacherByUsername(username); + if (isTeacher(user)) { + TeacherUserDetails tud = (TeacherUserDetails) user.getUserDetails(); + if (tud.isVerified()) { + return ResponseEntityGenerator.createError("Teacher already verified"); + } else { + this.teacherMailService.sendVerifyEmail(tud.getEmailAddress(), tud.getVerificationCode(), + request.getLocale(), request); + return createRegisterSuccessResponse(username); + } + } else { + return ResponseEntityGenerator.createError("Not a teacher"); + } + } +} diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/user/UserAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/user/UserAPIController.java index ca965b81fb..a08b962c13 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/user/UserAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/user/UserAPIController.java @@ -118,6 +118,7 @@ HashMap getUserInfo(Authentication auth, info.put("country", tud.getCountry()); info.put("schoolName", tud.getSchoolname()); info.put("schoolLevel", tud.getSchoollevel()); + info.put("isVerified", tud.isVerified()); } return info; } else { diff --git a/src/main/java/org/wise/portal/presentation/web/exception/TeacherVerificationException.java b/src/main/java/org/wise/portal/presentation/web/exception/TeacherVerificationException.java new file mode 100644 index 0000000000..7137fb3389 --- /dev/null +++ b/src/main/java/org/wise/portal/presentation/web/exception/TeacherVerificationException.java @@ -0,0 +1,11 @@ +package org.wise.portal.presentation.web.exception; + +import org.springframework.security.core.AuthenticationException; + +public class TeacherVerificationException extends AuthenticationException { + private static final long serialVersionUID = 1L; + + public TeacherVerificationException(String msg) { + super(msg); + } +} \ No newline at end of file diff --git a/src/main/java/org/wise/portal/presentation/web/filters/WISEAuthenticationProcessingFilter.java b/src/main/java/org/wise/portal/presentation/web/filters/WISEAuthenticationProcessingFilter.java index 61b874c006..3f251d5494 100644 --- a/src/main/java/org/wise/portal/presentation/web/filters/WISEAuthenticationProcessingFilter.java +++ b/src/main/java/org/wise/portal/presentation/web/filters/WISEAuthenticationProcessingFilter.java @@ -38,9 +38,11 @@ import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.wise.portal.domain.authentication.impl.TeacherUserDetails; import org.wise.portal.domain.user.User; import org.wise.portal.presentation.web.controllers.ControllerUtil; import org.wise.portal.presentation.web.exception.RecaptchaVerificationException; +import org.wise.portal.presentation.web.exception.TeacherVerificationException; import org.wise.portal.service.session.SessionService; import org.wise.portal.service.user.UserService; @@ -82,15 +84,28 @@ public Authentication attemptAuthentication(HttpServletRequest request, try { unsuccessfulAuthentication(request, response, new RecaptchaVerificationException("Recaptcha verification failed")); - } catch (IOException e) { - - } catch (ServletException e) { + } catch (IOException | ServletException e) {} + return null; + } + } + Authentication auth = super.attemptAuthentication(request, response); + return checkVerificationStatus(request, response, auth); + } - } + private Authentication checkVerificationStatus(HttpServletRequest request, HttpServletResponse response, + Authentication auth) { + User user = userService.retrieveTeacherByUsername(auth.getName()); + if (user != null) { + TeacherUserDetails tud = (TeacherUserDetails) user.getUserDetails(); + if (!tud.isVerified()) { + try { + unsuccessfulAuthentication(request, response, + new TeacherVerificationException("Teacher verification failed")); + } catch (IOException | ServletException e) {} return null; } } - return super.attemptAuthentication(request, response); + return auth; } @Override @@ -112,6 +127,19 @@ protected void successfulAuthentication(HttpServletRequest request, HttpServletR protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException { + checkForVerificationException(response, failed); super.unsuccessfulAuthentication(request, response, failed); } + + private void checkForVerificationException(HttpServletResponse response, AuthenticationException failed) + throws IOException { + if (failed instanceof TeacherVerificationException) { + response.setStatus(HttpServletResponse.SC_OK); + response.setContentType("application/json"); + response.setCharacterEncoding("UTF-8"); + response.getWriter().write( + "{\"isTeacherVerificationFailed\":true}" + ); + } + } } diff --git a/src/main/java/org/wise/portal/service/authentication/DuplicateVerificationCodeException.java b/src/main/java/org/wise/portal/service/authentication/DuplicateVerificationCodeException.java new file mode 100644 index 0000000000..e78b1fdb59 --- /dev/null +++ b/src/main/java/org/wise/portal/service/authentication/DuplicateVerificationCodeException.java @@ -0,0 +1,16 @@ +package org.wise.portal.service.authentication; + +public class DuplicateVerificationCodeException extends Exception { + + private static final long serialVersionUID = 1L; + + private String message; + + public DuplicateVerificationCodeException(String verificationCode) { + message = "Verification code:" + verificationCode + " already in use."; + } + + public String getMessage() { + return message; + } +} diff --git a/src/main/java/org/wise/portal/service/mail/IMailFacade.java b/src/main/java/org/wise/portal/service/mail/IMailFacade.java index 02af361d9b..30ecbbb21f 100644 --- a/src/main/java/org/wise/portal/service/mail/IMailFacade.java +++ b/src/main/java/org/wise/portal/service/mail/IMailFacade.java @@ -45,5 +45,4 @@ void postMail(String recipients[], String subject, String message , String from) void postMail(String recipients[], String subject, String message , String from, String[] cc) throws MessagingException; - } diff --git a/src/main/java/org/wise/portal/service/mail/teacher/TeacherMailService.java b/src/main/java/org/wise/portal/service/mail/teacher/TeacherMailService.java new file mode 100644 index 0000000000..5450260121 --- /dev/null +++ b/src/main/java/org/wise/portal/service/mail/teacher/TeacherMailService.java @@ -0,0 +1,16 @@ +package org.wise.portal.service.mail.teacher; + +import java.util.Locale; + +import javax.servlet.http.HttpServletRequest; + +public interface TeacherMailService { + + void sendWelcomeEmail(String email, String displayName, String username, + boolean socialAccount, Locale locale, HttpServletRequest request); + + void sendVerifyEmail(String email, String verificationCode, Locale locale, + HttpServletRequest request); + + boolean isSendEmailEnabled(); +} diff --git a/src/main/java/org/wise/portal/service/mail/teacher/impl/TeacherMailServiceImpl.java b/src/main/java/org/wise/portal/service/mail/teacher/impl/TeacherMailServiceImpl.java new file mode 100644 index 0000000000..b5535bfe2c --- /dev/null +++ b/src/main/java/org/wise/portal/service/mail/teacher/impl/TeacherMailServiceImpl.java @@ -0,0 +1,95 @@ +package org.wise.portal.service.mail.teacher.impl; + +import java.util.Locale; + +import javax.mail.MessagingException; +import javax.servlet.http.HttpServletRequest; + +import org.springframework.context.MessageSource; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Service; +import org.wise.portal.presentation.web.controllers.ControllerUtil; +import org.wise.portal.service.mail.MailService; +import org.wise.portal.service.mail.teacher.TeacherMailService; + +@Service +public class TeacherMailServiceImpl extends MailService implements TeacherMailService { + + private final Environment appProperties; + private final String emailCodePrefix = + "presentation.web.controllers.teacher.registerTeacherController."; + private final MessageSource messageSource; + private final String verifyBodyCode = this.emailCodePrefix + "verifyTeacherEmailBody"; + private final String verifySubjectCode = this.emailCodePrefix + "verifyTeacherEmailSubject"; + private final String welcomeBodyCode = this.emailCodePrefix + "welcomeTeacherEmailBody"; + private final String welcomeSocialAccountBodyCode = this.emailCodePrefix + "welcomeTeacherEmailBodyNoUsername"; + private final String welcomeSubjectCode = this.emailCodePrefix + "welcomeTeacherEmailSubject"; + + + TeacherMailServiceImpl(Environment appProperties, MessageSource messageSource) { + this.appProperties = appProperties; + this.messageSource = messageSource; + } + + @Override + public void sendWelcomeEmail(String email, String displayName, String username, + boolean socialAccount, Locale locale, HttpServletRequest request) { + if (isSendEmailEnabled()) { + String subject = getEmailMessage(this.welcomeSubjectCode, this.welcomeSubjectCode, null, locale); + String body = getWelcomeTeacherBody(displayName, username, socialAccount, locale, request); + this.sendEmail(email, subject, body); + } + } + + @Override + public void sendVerifyEmail(String email, String verificationCode, + Locale locale, HttpServletRequest request) { + if (isSendEmailEnabled()) { + String subject = getEmailMessage(this.verifySubjectCode, this.verifySubjectCode, null, locale); + String verificationUrl = getVerificationUrl(verificationCode, request); + Object[] args = new Object[] { verificationUrl }; + String body = getEmailMessage(this.verifyBodyCode, this.verifyBodyCode, args, locale); + this.sendEmail(email, subject, body); + } + } + + @Override + public boolean isSendEmailEnabled() { + String sendEmailEnabledStr = appProperties.getProperty("send_email_enabled", "false"); + return Boolean.valueOf(sendEmailEnabledStr); + } + + private String getEmailMessage(String defaultCode, String code, Object[] args, Locale locale) { + String defaultMessage = messageSource.getMessage(defaultCode, args, Locale.US); + return messageSource.getMessage(code, args, defaultMessage, locale); + } + + private String getWelcomeTeacherBody(String displayName, String username, boolean socialAccount, + Locale locale, HttpServletRequest request) { + String gettingStartedUrl = getGettingStartedUrl(request); + String code = socialAccount ? this.welcomeSocialAccountBodyCode : this.welcomeBodyCode; + Object[] args = socialAccount + ? new Object[] { displayName, gettingStartedUrl } + : new Object[] { displayName, username, gettingStartedUrl }; + return getEmailMessage(this.welcomeBodyCode, code, args, locale); + } + + private String getGettingStartedUrl(HttpServletRequest request) { + return ControllerUtil.getPortalUrlString(request) + "/help/getting-started"; + } + + private String getVerificationUrl(String verificationCode, HttpServletRequest request) { + return String.format("%s/api/teacher/verify?code=%s", + ControllerUtil.getPortalUrlString(request), verificationCode); + } + + private void sendEmail(String email, String subject, String body) { + String fromEmail = appProperties.getProperty("portalemailaddress"); + String[] recipients = { email }; + try { + this.postMail(recipients, subject, body, fromEmail); + } catch (MessagingException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/org/wise/portal/service/user/UserService.java b/src/main/java/org/wise/portal/service/user/UserService.java index 0d1cdbcc8e..4bcd3aa627 100644 --- a/src/main/java/org/wise/portal/service/user/UserService.java +++ b/src/main/java/org/wise/portal/service/user/UserService.java @@ -155,6 +155,8 @@ List retrieveStudentsByNameAndBirthday(String firstName, String lastName, User retrieveTeacherByUsername(String username); + User retrieveTeacherByVerificationCode(String verificationCode); + List retrieveTeachersByDisplayName(String displayName); List retrieveTeachersByCity(String city); diff --git a/src/main/java/org/wise/portal/service/user/impl/UserServiceImpl.java b/src/main/java/org/wise/portal/service/user/impl/UserServiceImpl.java index 79446d49a5..1839a1ee47 100644 --- a/src/main/java/org/wise/portal/service/user/impl/UserServiceImpl.java +++ b/src/main/java/org/wise/portal/service/user/impl/UserServiceImpl.java @@ -22,6 +22,8 @@ import java.util.Calendar; import java.util.List; +import java.util.Optional; +import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.EmptyResultDataAccessException; @@ -32,6 +34,7 @@ import org.springframework.transaction.annotation.Transactional; import org.wise.portal.dao.ObjectNotFoundException; import org.wise.portal.dao.authentication.GrantedAuthorityDao; +import org.wise.portal.dao.authentication.TeacherUserDetailsDao; import org.wise.portal.dao.authentication.UserDetailsDao; import org.wise.portal.dao.user.UserDao; import org.wise.portal.domain.authentication.MutableGrantedAuthority; @@ -43,6 +46,7 @@ import org.wise.portal.domain.user.impl.UserImpl; import org.wise.portal.presentation.web.exception.IncorrectPasswordException; import org.wise.portal.service.authentication.DuplicateUsernameException; +import org.wise.portal.service.authentication.DuplicateVerificationCodeException; import org.wise.portal.service.authentication.UserDetailsService; import org.wise.portal.service.user.UserService; @@ -56,16 +60,19 @@ public class UserServiceImpl implements UserService { @Autowired - private UserDetailsDao userDetailsDao; + private GrantedAuthorityDao grantedAuthorityDao; @Autowired - private GrantedAuthorityDao grantedAuthorityDao; + protected PasswordEncoder passwordEncoder; + + @Autowired + private TeacherUserDetailsDao teacherUserDetailsDao; @Autowired private UserDao userDao; @Autowired - protected PasswordEncoder passwordEncoder; + private UserDetailsDao userDetailsDao; @Transactional(readOnly = true) public User retrieveUser(UserDetails userDetails) { @@ -103,16 +110,15 @@ public User createUser(final MutableUserDetails userDetails) { details.setNumberOfLogins(0); details.setSignupdate(Calendar.getInstance().getTime()); - String currentUsernameSuffix = null; + String currentUsernameSuffix = ""; User createdUser = null; boolean done = false; while (!done) { try { - currentUsernameSuffix = details.getNextUsernameSuffix(currentUsernameSuffix); String coreUsername = details.getCoreUsername(); details.setUsername(coreUsername + currentUsernameSuffix); - checkUserErrors(userDetails.getUsername()); + checkUserErrors(userDetails.getUsername(), getVerificationCode(userDetails)); assignRole(userDetails, UserDetailsService.USER_ROLE); encodePassword(userDetails); @@ -121,13 +127,24 @@ public User createUser(final MutableUserDetails userDetails) { userDao.save(createdUser); done = true; } catch (DuplicateUsernameException e) { - // the username already exists; try the next possible username - continue; + currentUsernameSuffix = details.getNextUsernameSuffix(currentUsernameSuffix); + } catch (DuplicateVerificationCodeException e) { + TeacherUserDetails tud = (TeacherUserDetails) details; + tud.setVerificationCode(); } } return createdUser; } + private Optional getVerificationCode(MutableUserDetails userDetails) { + if ((userDetails instanceof TeacherUserDetails)) { + TeacherUserDetails tud = (TeacherUserDetails) userDetails; + return Optional.of(tud.getVerificationCode()); + } else { + return Optional.empty(); + } + } + void encodePassword(MutableUserDetails userDetails) { userDetails.setPassword(passwordEncoder.encode(userDetails.getPassword())); } @@ -142,12 +159,18 @@ public void assignRole(MutableUserDetails userDetails, final String role) { * a user with the same username * * @param username The username to check for in the data store + * @param verificationCode The verification code to check for in the data store * @throws DuplicateUsernameException if the username is the same as a username already in data - * store. + * @throws DuplicateVerificationCodeException if the verification code is the same as a + * verification code already in data store. */ - private void checkUserErrors(final String username) throws DuplicateUsernameException { + private void checkUserErrors(final String username, final Optional verificationCode) + throws DuplicateUsernameException, DuplicateVerificationCodeException { if (userDetailsDao.hasUsername(username)) { throw new DuplicateUsernameException(username); + } else if (verificationCode.isPresent() + && teacherUserDetailsDao.hasVerificationCode(verificationCode.get())) { + throw new DuplicateVerificationCodeException(verificationCode.get()); } } @@ -208,6 +231,10 @@ public User retrieveTeacherByUsername(String username) { return userDao.retrieveTeacherByUsername(username); } + public User retrieveTeacherByVerificationCode(String verificationCode) { + return userDao.retrieveTeacherByVerificationCode(verificationCode); + } + public List retrieveTeachersByDisplayName(String displayName) { return userDao.retrieveTeachersByDisplayName(displayName); } diff --git a/src/main/resources/db/migration/V2__teacher_email_verification.sql b/src/main/resources/db/migration/V2__teacher_email_verification.sql new file mode 100644 index 0000000000..d3854cb5f8 --- /dev/null +++ b/src/main/resources/db/migration/V2__teacher_email_verification.sql @@ -0,0 +1,3 @@ +ALTER TABLE teacher_user_details +ADD COLUMN isVerified BIT(1) NOT NULL DEFAULT TRUE, +ADD COLUMN verificationCode VARCHAR(255) NULL; diff --git a/src/main/resources/i18n/i18n.properties b/src/main/resources/i18n/i18n.properties index fa378a33dd..8f5a9b327a 100644 --- a/src/main/resources/i18n/i18n.properties +++ b/src/main/resources/i18n/i18n.properties @@ -3758,6 +3758,11 @@ presentation.web.controllers.teacher.project.customized.ShareProjectController.s presentation.web.controllers.teacher.project.customized.ShareProjectController.shareProjectConfirmationEmailBody={0} shared a project with you on WISE:\n\nProject Name: {1}\nProject ID: {2}\nShared with username: {3}\nDate this project was shared: {4}\n\nGo to this URL to preview this project: {5}\n\nThanks,\nWISE Team presentation.web.controllers.teacher.project.customized.ShareProjectController.shareProjectConfirmationEmailBody.description=Message body of the email notifying user that someone shared a project with them. +presentation.web.controllers.teacher.registerTeacherController.verifyTeacherEmailSubject=Verify your WISE account +presentation.web.controllers.teacher.registerTeacherController.verifyTeacherEmailSubject.description=Subject line of the email including the link that teachers must be click to verify their account. +presentation.web.controllers.teacher.registerTeacherController.verifyTeacherEmailBody=Please click the following link to verify your email address and complete the registration process:\n{0}\n\nIf you did not make this request, you can ignore this email. +presentation.web.controllers.teacher.registerTeacherController.verifyTeacherEmailBody.description=Message body of the email including the link that teachers must be click to verify their account. + presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailSubject=Welcome to WISE! presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailSubject.description=Text "Welcome to WISE!" to welcome new teachers in a welcome email. presentation.web.controllers.teacher.registerTeacherController.welcomeTeacherEmailBody=Hi {0}!\n\nYour username is: {1}\n\nWelcome to our WISE learning environment. Our research over the past many years demonstrates that students have significant learning gains when using WISE projects.\n\nFor more information on how to get started, please visit our Getting Started page.\n{2}\n\nThank you for joining WISE,\nWISE Team diff --git a/src/test/java/org/wise/portal/dao/authentication/impl/HibernateUserDetailsDaoTest.java b/src/test/java/org/wise/portal/dao/authentication/impl/HibernateUserDetailsDaoTest.java index 796f3c981e..48099afcf2 100644 --- a/src/test/java/org/wise/portal/dao/authentication/impl/HibernateUserDetailsDaoTest.java +++ b/src/test/java/org/wise/portal/dao/authentication/impl/HibernateUserDetailsDaoTest.java @@ -77,7 +77,7 @@ public void retrieveByName_WithExistingStudentName_ShouldReturnUser() { @Test public void retrieveAllTeacherUsernames_WhenThereAreTeachers_ShouldReturnTeacherUsernames() { List usernames = userDetailsDao.retrieveAllTeacherUsernames(); - assertEquals(4, usernames.size()); + assertEquals(2, usernames.size()); } @Test diff --git a/src/test/java/org/wise/portal/dao/user/impl/HibernateUserDaoTest.java b/src/test/java/org/wise/portal/dao/user/impl/HibernateUserDaoTest.java index 641be7c6f0..dd9d6dacc5 100644 --- a/src/test/java/org/wise/portal/dao/user/impl/HibernateUserDaoTest.java +++ b/src/test/java/org/wise/portal/dao/user/impl/HibernateUserDaoTest.java @@ -32,8 +32,10 @@ import org.springframework.security.core.userdetails.UserDetails; import org.wise.portal.domain.authentication.Gender; import org.wise.portal.domain.authentication.Schoollevel; +import org.wise.portal.domain.authentication.impl.TeacherUserDetails; import org.wise.portal.domain.user.User; import org.wise.portal.junit.AbstractTransactionalDbTests; +import org.wise.portal.service.authentication.DuplicateUsernameException; /** * @author Geoffrey Kwan @@ -48,17 +50,29 @@ public class HibernateUserDaoTest extends AbstractTransactionalDbTests { @BeforeEach public void setUp() throws Exception { - teacher1 = createTeacherUser("Mrs", "Puff", "MrsPuff", "Mrs. Puff", "boat", "Bikini Bottom", + teacher1 = createVerificationTeacherUser("Mrs", "Puff", "MrsPuff", "Mrs. Puff", "boat", "Bikini Bottom", "Water State", "Pacific Ocean", "mrspuff@bikinibottom.com", "Boating School", - Schoollevel.COLLEGE, "1234567890"); - teacher2 = createTeacherUser("Mr", "Krabs", "MrKrabs", "Mr. Krabs", "restaurant", + Schoollevel.COLLEGE, "1234567890", true); + teacher2 = createVerificationTeacherUser("Mr", "Krabs", "MrKrabs", "Mr. Krabs", "restaurant", "Bikini Bottom", "Water State", "Pacific Ocean", "mrkrabs@bikinibottom.com", "Krusty Krab", - Schoollevel.HIGH_SCHOOL, "abcdefghij"); + Schoollevel.HIGH_SCHOOL, "abcdefghij", false); student1 = createStudentUser("Spongebob", "Squarepants", "SpongebobS0101", "burger", 1, 1, Gender.MALE); student2 = createStudentUser("Patrick", "Star", "PatrickS0101", "rock", 1, 1, Gender.MALE); } + public User createVerificationTeacherUser(String firstName, String lastName, String username, + String displayName, String password, String city, String state, String country, String email, + String schoolName, Schoollevel schoolLevel, String googleUserId, boolean isVerified) + throws DuplicateUsernameException { + User teacher = createTeacherUser(firstName, lastName, username, displayName, password, city, + state, country, email, schoolName, schoolLevel, googleUserId); + TeacherUserDetails tud = (TeacherUserDetails) teacher.getUserDetails(); + tud.setVerificationCode(); + tud.setVerified(isVerified); + return teacher; + } + @Test public void retrieveByUserDetails_ShouldReturnTheUser() { UserDetails userDetails = teacher1.getUserDetails(); @@ -121,7 +135,7 @@ public void retrieveDisabledUsers_WhenThereIsOneDisabledUser_ShouldReturnOneUser @Test public void retrieveAllTeachers_ShouldReturnAllTeachers() { List users = userDao.retrieveAllTeachers(); - assertEquals(4, users.size()); + assertEquals(2, users.size()); } @Test @@ -244,6 +258,19 @@ public void retrieveTeachersByEmail_WithExistingEmail_ShouldSucceed() { assertEquals(1, users.size()); } + @Test + public void retrieveTeacherByVerificationCode_WithNonExistingCode_ShouldNotReturnAnyUser() { + User user = userDao.retrieveTeacherByVerificationCode("wxyz6789"); + assertNull(user); + } + + @Test + public void retrieveTeacherByVerificationCode_WithExistingCode_ShouldSucceed() { + TeacherUserDetails tud = (TeacherUserDetails) teacher1.getUserDetails(); + User user = userDao.retrieveTeacherByVerificationCode(tud.getVerificationCode()); + assertEquals(teacher1.getId(), user.getId()); + } + @Test public void retrieveTeachersBySchoolLevel_WithNotUsedSchoolLevel_ShouldNotReturnAnyUser() { List users = userDao @@ -254,7 +281,7 @@ public void retrieveTeachersBySchoolLevel_WithNotUsedSchoolLevel_ShouldNotReturn @Test public void retrieveTeachersBySchoolLevel_WithExistingSchoolLevel_ShouldSucceed() { List users = userDao.retrieveTeachersBySchoolLevel(Schoollevel.COLLEGE.toString()); - assertEquals(3, users.size()); + assertEquals(1, users.size()); } @Test diff --git a/src/test/java/org/wise/portal/junit/AbstractTransactionalDbTests.java b/src/test/java/org/wise/portal/junit/AbstractTransactionalDbTests.java index 1b174d3a45..a5ced9a0a0 100644 --- a/src/test/java/org/wise/portal/junit/AbstractTransactionalDbTests.java +++ b/src/test/java/org/wise/portal/junit/AbstractTransactionalDbTests.java @@ -151,6 +151,7 @@ public User createTeacherUser(String firstName, String lastName, String username userDetails.setSchoolname(schoolName); userDetails.setSchoollevel(schoolLevel); userDetails.setGoogleUserId(googleUserId); + userDetails.setVerificationCode(); User user = userService.createUser(userDetails); userDao.save(user); return user; diff --git a/src/test/java/org/wise/portal/presentation/web/controllers/APIControllerTest.java b/src/test/java/org/wise/portal/presentation/web/controllers/APIControllerTest.java index 4a9c0cf5f0..d025c696f4 100644 --- a/src/test/java/org/wise/portal/presentation/web/controllers/APIControllerTest.java +++ b/src/test/java/org/wise/portal/presentation/web/controllers/APIControllerTest.java @@ -136,7 +136,7 @@ public void setUp() { createWorkgroups(); } - private void createStudents() { + protected void createStudents() { student1UserDetails = createStudentUserDetails(STUDENT_FIRSTNAME, STUDENT_LASTNAME, STUDENT_USERNAME, Gender.MALE, 5, STUDENT1_GOOGLE_ID, STUDENT1_ACCOUNT_ANSWER); student1 = createStudent(student1Id, student1UserDetails); @@ -147,13 +147,13 @@ private void createStudents() { studentAuth2 = createAuthentication(student2UserDetails); } - private void createTeachers() { + protected void createTeachers() { teacher1UserDetails = createTeacherUserDetails(TEACHER_FIRSTNAME, TEACHER_LASTNAME, - TEACHER_USERNAME, Schoollevel.COLLEGE, 5); + TEACHER_USERNAME, Schoollevel.COLLEGE, 5, true, "abcd1234"); teacher1 = createTeacher(teacher1Id, teacher1UserDetails); teacherAuth = createAuthentication(teacher1UserDetails); teacher2UserDetails = createTeacherUserDetails(TEACHER2_FIRSTNAME, TEACHER2_LASTNAME, - TEACHER2_USERNAME, Schoollevel.COLLEGE, 5); + TEACHER2_USERNAME, Schoollevel.COLLEGE, 5, false, "efgh5678"); teacher2 = createTeacher(teacher2Id, teacher2UserDetails); teacherAuth2 = createAuthentication(teacher2UserDetails); } @@ -262,13 +262,16 @@ protected User createTeacher(Long id, TeacherUserDetails teacherUserDetails) { } protected TeacherUserDetails createTeacherUserDetails(String firstName, String lastName, - String username, Schoollevel schoolLevel, Integer numberOfLogins) { + String username, Schoollevel schoolLevel, Integer numberOfLogins, boolean isVerified, + String verificationCode) { TeacherUserDetails teacherUserDetails = new TeacherUserDetails(); teacherUserDetails.setFirstname(firstName); teacherUserDetails.setLastname(lastName); teacherUserDetails.setUsername(username); teacherUserDetails.setSchoollevel(schoolLevel); teacherUserDetails.setNumberOfLogins(numberOfLogins); + teacherUserDetails.setVerified(isVerified); + teacherUserDetails.setVerificationCode(); PersistentGrantedAuthority teacherAuthority = new PersistentGrantedAuthority(); teacherAuthority.setAuthority(UserDetailsService.TEACHER_ROLE); teacherUserDetails.setAuthorities(new GrantedAuthority[] { teacherAuthority }); diff --git a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIControllerTest.java b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIControllerTest.java index 87bb71b758..6ba5fef25b 100644 --- a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIControllerTest.java +++ b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRegistrationAPIControllerTest.java @@ -1,6 +1,7 @@ package org.wise.portal.presentation.web.controllers.teacher; import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.expectLastCall; import static org.easymock.EasyMock.isA; import static org.easymock.EasyMock.replay; import static org.easymock.EasyMock.verify; @@ -10,11 +11,14 @@ import java.util.Locale; import java.util.Map; +import javax.servlet.http.HttpServletResponse; + import org.easymock.EasyMockExtension; import org.easymock.Mock; import org.easymock.TestSubject; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.context.MessageSource; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.wise.portal.domain.authentication.impl.TeacherUserDetails; @@ -22,6 +26,7 @@ import org.wise.portal.presentation.web.exception.InvalidNameException; import org.wise.portal.service.authentication.DuplicateUsernameException; import org.wise.portal.service.authentication.UserDetailsService; +import org.wise.portal.service.mail.teacher.TeacherMailService; import org.wise.portal.service.password.PasswordService; import org.wise.portal.service.password.impl.PasswordServiceImpl; import org.wise.portal.service.usertags.UserTagsService; @@ -32,6 +37,15 @@ public class TeacherRegistrationAPIControllerTest extends APIControllerTest { @TestSubject private TeacherRegistrationAPIController teacherRegistrationAPIController = new TeacherRegistrationAPIController(); + @Mock + private HttpServletResponse response; + + @Mock + private TeacherMailService teacherMailService; + + @Mock + private MessageSource messageSource; + @Mock private UserDetailsService userDetailsService; @@ -66,11 +80,16 @@ public void createTeacherAccount_WithGoogleUserId_CreateUser() replay(request); expect(userService.createUser(isA(TeacherUserDetails.class))).andReturn(teacher1); replay(userService); + teacherMailService.sendWelcomeEmail("", TEACHER_FIRSTNAME + " " + TEACHER_LASTNAME, TEACHER_USERNAME, + true, Locale.US, request); + expectLastCall(); + replay(teacherMailService); ResponseEntity> response = teacherRegistrationAPIController .createTeacherAccount(teacherFields, request); assertEquals(TEACHER_USERNAME, response.getBody().get("username")); verify(request); verify(userService); + verify(teacherMailService); } private HashMap createDefaultTeacherFields() { diff --git a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationAPIControllerTest.java b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationAPIControllerTest.java new file mode 100644 index 0000000000..ccb28fa7b9 --- /dev/null +++ b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationAPIControllerTest.java @@ -0,0 +1,78 @@ +package org.wise.portal.presentation.web.controllers.teacher; + +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.expectLastCall; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.verify; + +import java.io.IOException; + +import javax.servlet.http.HttpServletResponse; + +import org.easymock.EasyMockExtension; +import org.easymock.Mock; +import org.easymock.TestSubject; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.wise.portal.domain.authentication.impl.TeacherUserDetails; +import org.wise.portal.presentation.web.TeacherVerificationAPIController; +import org.wise.portal.presentation.web.controllers.APIControllerTest; +import org.wise.portal.service.mail.teacher.TeacherMailService; + +@ExtendWith(EasyMockExtension.class) +public class TeacherVerificationAPIControllerTest extends APIControllerTest { + + @TestSubject + private TeacherVerificationAPIController teacherVerificationAPIController = new TeacherVerificationAPIController(); + + @Mock + private HttpServletResponse response; + + @Mock + private TeacherMailService teacherMailService; + + @Test + public void verifyTeacherAndRedirect_TeacherUnverified_RedirectsWithVerifiedTrue() throws IOException { + createTeachers(); + expect(userService.retrieveTeacherByVerificationCode("efgh5678")).andReturn(teacher2); + userService.updateUser(teacher2); + expectLastCall(); + replay(userService); + TeacherUserDetails tud = (TeacherUserDetails) teacher2.getUserDetails(); + teacherMailService.sendWelcomeEmail(tud.getEmailAddress(), tud.getDisplayname(), tud.getUsername(), false, null, request); + expectLastCall(); + replay(teacherMailService); + response.sendRedirect("/login?verified=true&username=" + tud.getUsername()); + expectLastCall(); + replay(response); + teacherVerificationAPIController.verifyTeacherAndRedirect("efgh5678", response, request); + verify(userService); + verify(teacherMailService); + verify(response); + } + + @Test + public void verifyTeacherAndRedirect_TeacherAlreadyVerified_RedirectsWithVerifiedFalse() throws IOException { + createTeachers(); + expect(userService.retrieveTeacherByVerificationCode("abcd1234")).andReturn(teacher1); + replay(userService); + response.sendRedirect("/login?verified=false&username=" + teacher1.getUserDetails().getUsername()); + expectLastCall(); + replay(response); + teacherVerificationAPIController.verifyTeacherAndRedirect("abcd1234", response, request); + verify(userService); + verify(response); + } + + @Test + public void verifyTeacherAndRedirect_InvalidVerificationCode_RedirectsWithVerificationError() throws IOException { + expect(userService.retrieveTeacherByVerificationCode("")).andReturn(null); + replay(userService); + response.sendRedirect("/login?verified=error"); + expectLastCall(); + replay(response); + teacherVerificationAPIController.verifyTeacherAndRedirect("", response, request); + verify(userService); + verify(response); + } +} diff --git a/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIControllerTest.java b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIControllerTest.java new file mode 100644 index 0000000000..5505842730 --- /dev/null +++ b/src/test/java/org/wise/portal/presentation/web/controllers/teacher/TeacherVerificationEmailAPIControllerTest.java @@ -0,0 +1,59 @@ +package org.wise.portal.presentation.web.controllers.teacher; + +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.verify; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Map; + +import org.easymock.EasyMockExtension; +import org.easymock.Mock; +import org.easymock.TestSubject; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.http.ResponseEntity; +import org.wise.portal.presentation.web.controllers.APIControllerTest; +import org.wise.portal.service.mail.teacher.TeacherMailService; + +@ExtendWith(EasyMockExtension.class) +public class TeacherVerificationEmailAPIControllerTest extends APIControllerTest { + + @TestSubject + private TeacherVerificationEmailAPIController teacherVerificationEmailAPIController = new TeacherVerificationEmailAPIController(); + + @Mock + private TeacherMailService teacherMailService; + + @Test + public void sendVerificationEmail_UserIsUnverifiedTeacher_SendEmail() { + this.createTeachers(); + expect(userService.retrieveTeacherByUsername(TEACHER2_USERNAME)).andReturn(teacher2); + replay(userService); + ResponseEntity> response = + teacherVerificationEmailAPIController.sendVerificationEmail(TEACHER2_USERNAME, request); + assertEquals(TEACHER2_USERNAME, response.getBody().get("username")); + verify(userService); + } + + @Test + public void sendVerificationEmail_UserIsVerifiedTeacher_ReturnError() { + this.createTeachers(); + expect(userService.retrieveTeacherByUsername(TEACHER_USERNAME)).andReturn(teacher1); + replay(userService); + ResponseEntity> response = + teacherVerificationEmailAPIController.sendVerificationEmail(TEACHER_USERNAME, request); + assertEquals("Teacher already verified", response.getBody().get("messageCode")); + verify(userService); + } + + @Test + public void sendVerificationEmail_UserIsNotTeacher_ReturnError() { + expect(userService.retrieveTeacherByUsername(STUDENT_USERNAME)).andReturn(null); + replay(userService); + ResponseEntity> response = + teacherVerificationEmailAPIController.sendVerificationEmail(STUDENT_USERNAME, request); + assertEquals("Not a teacher", response.getBody().get("messageCode")); + verify(userService); + } +} diff --git a/src/test/java/org/wise/portal/service/impl/UserServiceImplTest.java b/src/test/java/org/wise/portal/service/impl/UserServiceImplTest.java index 88c63ef47b..a7a3727f9f 100644 --- a/src/test/java/org/wise/portal/service/impl/UserServiceImplTest.java +++ b/src/test/java/org/wise/portal/service/impl/UserServiceImplTest.java @@ -23,6 +23,7 @@ package org.wise.portal.service.impl; import static junit.framework.TestCase.assertTrue; +import static org.easymock.EasyMock.anyString; import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.replay; import static org.easymock.EasyMock.verify; @@ -39,6 +40,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.security.crypto.password.PasswordEncoder; import org.wise.portal.dao.authentication.GrantedAuthorityDao; +import org.wise.portal.dao.authentication.TeacherUserDetailsDao; import org.wise.portal.dao.authentication.UserDetailsDao; import org.wise.portal.dao.user.UserDao; import org.wise.portal.domain.authentication.Gender; @@ -62,16 +64,19 @@ public class UserServiceImplTest { private UserService userService = new UserServiceImpl(); @Mock - private UserDao userDao; + private GrantedAuthorityDao grantedAuthorityDao; @Mock - private UserDetailsDao userDetailsDao; + private PasswordEncoder passwordEncoder; @Mock - private GrantedAuthorityDao grantedAuthorityDao; + private TeacherUserDetailsDao teacherUserDetailsDao; @Mock - private PasswordEncoder passwordEncoder; + private UserDao userDao; + + @Mock + private UserDetailsDao userDetailsDao; private static final String PASSWORD = "password"; @@ -134,7 +139,7 @@ public void setupCreateTest() { teacherUserDetails.setPassword(PASSWORD); teacherUserDetails.setFirstname(FIRSTNAME); teacherUserDetails.setLastname(LASTNAME); - + teacherUserDetails.setVerificationCode(); userAuthority = new PersistentGrantedAuthority(); userAuthority.setAuthority(UserDetailsService.USER_ROLE); studentAuthority = new PersistentGrantedAuthority(); @@ -182,11 +187,14 @@ public void createUser_ValidTeacherUser_ShouldCreateTeacherUser() throws Excepti expectTeacherAuthorityLookup(); expect(userDetailsDao.hasUsername("BillyBob")).andReturn(false); replay(userDetailsDao); + expect(teacherUserDetailsDao.hasVerificationCode(anyString())).andReturn(false); + replay(teacherUserDetailsDao); expect(passwordEncoder.encode(teacherUserDetails.getPassword())).andReturn(ENCODED_PASSWORD); replay(passwordEncoder); userService.createUser(teacherUserDetails); verify(grantedAuthorityDao); verify(userDetailsDao); + verify(teacherUserDetailsDao); assertEquals("BillyBob", teacherUserDetails.getUsername()); assertEquals(ENCODED_PASSWORD, teacherUserDetails.getPassword()); assertUserHasTeacherAuthorities(); @@ -200,11 +208,14 @@ public void createUser_TeacherUserWithSpacesInName_ShouldCreateTeacherUserWithou expectTeacherAuthorityLookup(); expect(userDetailsDao.hasUsername("BillyBob")).andReturn(false); replay(userDetailsDao); + expect(teacherUserDetailsDao.hasVerificationCode(anyString())).andReturn(false); + replay(teacherUserDetailsDao); expect(passwordEncoder.encode(teacherUserDetails.getPassword())).andReturn(ENCODED_PASSWORD); replay(passwordEncoder); userService.createUser(teacherUserDetails); verify(grantedAuthorityDao); verify(userDetailsDao); + verify(teacherUserDetailsDao); assertEquals("BillyBob", teacherUserDetails.getUsername()); assertEquals(ENCODED_PASSWORD, teacherUserDetails.getPassword()); assertUserHasTeacherAuthorities();