diff --git a/src/main/java/edu/tamu/cap/auth/controller/AuthController.java b/src/main/java/edu/tamu/cap/auth/controller/AuthController.java new file mode 100644 index 00000000..334c0bea --- /dev/null +++ b/src/main/java/edu/tamu/cap/auth/controller/AuthController.java @@ -0,0 +1,180 @@ +package edu.tamu.cap.auth.controller; + +import static edu.tamu.weaver.response.ApiStatus.ERROR; +import static edu.tamu.weaver.response.ApiStatus.INVALID; +import static edu.tamu.weaver.response.ApiStatus.SUCCESS; +import static org.springframework.web.bind.annotation.RequestMethod.GET; +import static org.springframework.web.bind.annotation.RequestMethod.POST; + +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import edu.tamu.cap.auth.service.AppUserCredentialsService; +import edu.tamu.cap.model.EmailTemplate; +import edu.tamu.cap.model.User; +import edu.tamu.cap.model.repo.UserRepo; +import edu.tamu.cap.service.EmailTemplateService; +import edu.tamu.weaver.auth.controller.WeaverAuthController; +import edu.tamu.weaver.response.ApiResponse; +import edu.tamu.weaver.validation.results.ValidationResults; +import edu.tamu.weaver.validation.utility.ValidationUtility; + +@RestController +@RequestMapping("/auth") +public class AuthController extends WeaverAuthController { + + private Logger logger = LoggerFactory.getLogger(this.getClass()); + + private final static String EMAIL_VERIFICATION_TYPE = "EMAIL_VERIFICATION"; + + public static final String REGISTRATION_TEMPLATE = "SYSTEM New User Registration"; + + @Value("${app.url}") + private String url; + + @Autowired + private UserRepo userRepo; + + @Autowired + EmailTemplateService emailTemplateService; + + @Autowired + private AppUserCredentialsService appUserCredentialsService; + + @RequestMapping(value = "/register", method = { POST, GET }) + public ApiResponse registration(@RequestBody(required = false) Map data, @RequestParam Map parameters) { + + if (parameters.get("email") != null) { + + String email = parameters.get("email"); + + if (userRepo.findByEmail(email) != null) { + logger.debug("Account with email " + email + " already exists!"); + ValidationResults invalidEmail = new ValidationResults(); + invalidEmail.addMessage(ValidationUtility.BUSINESS_MESSAGE_KEY, "verify", "Account with email " + email + " already exists!"); + return new ApiResponse(INVALID, invalidEmail); + } + + HashMap emailData = new HashMap(); + try { + emailData.put("REGISTRATION_URL", url + "/register?token=" + cryptoService.generateGenericToken(email, EMAIL_VERIFICATION_TYPE)); + } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException + | BadPaddingException e) { + logger.debug("Unable to generate token! " + email); + return new ApiResponse(ERROR, "Unable to generate token! " + email); + } + EmailTemplate finalEmail = emailTemplateService.buildEmail("user_registration",emailData); + + try { + emailSender.sendEmail(email, finalEmail.getSubject(), finalEmail.getMessage()); + } catch (javax.mail.MessagingException e) { + logger.debug("Unable to send email! " + email); + return new ApiResponse(ERROR, "Unable to send email! " + email); + } + + return new ApiResponse(SUCCESS, "An email has been sent to " + email + ". Please confirm email to continue registration.", parameters); + } + + String token = data.get("token"); + String firstName = data.get("firstName"); + String lastName = data.get("lastName"); + String password = data.get("userPassword"); + String confirm = data.get("confirm"); + + if ((firstName == null || firstName.trim().length() == 0) && (lastName == null || lastName.trim().length() == 0)) { + logger.debug("Either a first or last name is required!"); + return new ApiResponse(ERROR, "Either a first or last name is required!"); + } + + if (password == null || password.trim().length() == 0) { + logger.debug("Registration requires a password!"); + return new ApiResponse(ERROR, "Registration requires a password!"); + } + + if (password != null && !password.equals(confirm)) { + logger.debug("The passwords do not match!"); + return new ApiResponse(ERROR, "The passwords do not match!"); + } + + if (password != null && password.trim().length() < 6) { + logger.debug("Password must be greater than 6 characters!"); + return new ApiResponse(ERROR, "Password must be greater than 6 characters!"); + } + + String[] content = null; + try { + content = cryptoService.validateGenericToken(token, EMAIL_VERIFICATION_TYPE); + } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException e) { + logger.debug("Unable to validate token!"); + return new ApiResponse(ERROR, "Unable to generate token!"); + } + + String tokenCreateTime = content[0]; + String email = content[1]; + + Long tokenDaysOld = TimeUnit.MILLISECONDS.toDays(Long.valueOf(tokenCreateTime) - new Date().getTime()); + + if (tokenDaysOld >= 2) { + logger.debug("Token has expired!"); + return new ApiResponse(ERROR, "Token has expired! Please begin registration again."); + } + + User user = appUserCredentialsService.createUserFromRegistration(email, firstName, lastName, cryptoService.encodePassword(password)); + + return new ApiResponse(SUCCESS, "Registration was successful. Please login.", user); + } + + @RequestMapping(value = "/login", method = POST) + public ApiResponse login(@RequestBody Map data) { + + String email = data.get("email"); + String password = data.get("userPassword"); + + User user = userRepo.findByEmail(email); + + if (user == null) { + logger.debug("No user found with email " + email + "!"); + ValidationResults invalidEmail = new ValidationResults(); + invalidEmail.addMessage(ValidationUtility.BUSINESS_MESSAGE_KEY, "login", "No user found with email " + email + "!"); + return new ApiResponse(INVALID, invalidEmail); + } + + if (!cryptoService.validatePassword(password, user.getPassword())) { + logger.debug("Authentication failed!"); + ValidationResults failedAuthenticationResults = new ValidationResults(); + failedAuthenticationResults.addMessage(ValidationUtility.BUSINESS_MESSAGE_KEY, "login", "Authentication failed!"); + return new ApiResponse(INVALID, failedAuthenticationResults); + } + + try { + Map claims = new HashMap(); + claims.put("lastName", user.getLastName()); + claims.put("firstName", user.getFirstName()); + claims.put("username", user.getUsername()); + claims.put("email", user.getEmail()); + String subject = user.getEmail(); + return new ApiResponse(SUCCESS, "Login successful", tokenService.createToken(subject, claims)); + } catch (Exception e) { + logger.debug("Unable to generate token!"); + return new ApiResponse(ERROR, "Unable to generate token!"); + } + } + +} diff --git a/src/main/java/edu/tamu/cap/auth/service/AppUserCredentialsService.java b/src/main/java/edu/tamu/cap/auth/service/AppUserCredentialsService.java index fa77f9eb..b65d81f2 100644 --- a/src/main/java/edu/tamu/cap/auth/service/AppUserCredentialsService.java +++ b/src/main/java/edu/tamu/cap/auth/service/AppUserCredentialsService.java @@ -59,6 +59,17 @@ public synchronized User updateUserByCredentials(Credentials credentials) { } + public User createUserFromRegistration(String email, String firstName, String lastName, String password) { + Role role = Role.ROLE_USER; + for (String adminEmail : admins) { + if (adminEmail.equals(email)) { + role = Role.ROLE_ADMIN; + break; + } + } + return userRepo.create(email, firstName, lastName, password, role.toString()); + } + @Override public String getAnonymousRole() { return Role.ROLE_ANONYMOUS.toString(); diff --git a/src/main/java/edu/tamu/cap/model/EmailTemplate.java b/src/main/java/edu/tamu/cap/model/EmailTemplate.java new file mode 100644 index 00000000..fa47b777 --- /dev/null +++ b/src/main/java/edu/tamu/cap/model/EmailTemplate.java @@ -0,0 +1,72 @@ +package edu.tamu.cap.model; + +public class EmailTemplate { + + private String name; + + private String subject; + + private String message; + + /** + * Create a new EmailTemplate + * + * @param name + * The new template's name. + * @param subject + * The new template's subject. + * @param message + * The new template's message + */ + public EmailTemplate(String name, String subject, String message) { + setName(name); + setSubject(subject); + setMessage(message); + } + + /** + * @return the name + */ + public String getName() { + return name; + } + + /** + * @param name + * the name to set + */ + public void setName(String name) { + this.name = name; + } + + /** + * @return the subject + */ + public String getSubject() { + return subject; + } + + /** + * @param subject + * the subject to set + */ + public void setSubject(String subject) { + this.subject = subject; + } + + /** + * @return the message + */ + public String getMessage() { + return message; + } + + /** + * @param message + * the message to set + */ + public void setMessage(String message) { + this.message = message; + } + +} diff --git a/src/main/java/edu/tamu/cap/model/User.java b/src/main/java/edu/tamu/cap/model/User.java index 552244e6..6a0f100d 100644 --- a/src/main/java/edu/tamu/cap/model/User.java +++ b/src/main/java/edu/tamu/cap/model/User.java @@ -19,12 +19,14 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonView; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import edu.tamu.weaver.user.model.IRole; import edu.tamu.weaver.auth.model.AbstractWeaverUserDetails; +import edu.tamu.weaver.response.ApiView; /** * Application User entity. @@ -46,6 +48,14 @@ public class User extends AbstractWeaverUserDetails { @Column(name = "last_name") private String lastName; + @JsonView(ApiView.Partial.class) + @Column(nullable = false, unique = true) + private String email; + + @Column + @JsonIgnore + private String password = null; + /** * Constructor for the application user * @@ -76,6 +86,14 @@ public User(String uin, String firstName, String lastName, String role) { setRole(role == null ? null : Role.valueOf(role)); } + public User(String email, String firstName, String lastName, String role, String password) { + this(email); + setFirstName(firstName); + setLastName(lastName); + setRole(role == null ? null : Role.valueOf(role)); + setPassword(password); + } + public User(User user) { this(user.getUsername()); setFirstName(user.getFirstName()); @@ -137,6 +155,38 @@ public void setLastName(String lastName) { this.lastName = lastName; } + /** + * @return the email + */ + public String getEmail() { + return email; + } + + /** + * @param email + * the email to set + */ + public void setEmail(String email) { + this.email = email; + } + + @Override + @JsonIgnore + public String getPassword() { + return null; + } + + /** + * Stores an encoded password + * + * @param password + * the password to set + */ + public void setPassword(String password) { + this.password = password; + } + + @Override @JsonIgnore public Collection getAuthorities() { @@ -146,10 +196,4 @@ public Collection getAuthorities() { return authorities; } - @Override - @JsonIgnore - public String getPassword() { - return null; - } - } diff --git a/src/main/java/edu/tamu/cap/model/repo/UserRepo.java b/src/main/java/edu/tamu/cap/model/repo/UserRepo.java index 369fa35d..b11d9553 100644 --- a/src/main/java/edu/tamu/cap/model/repo/UserRepo.java +++ b/src/main/java/edu/tamu/cap/model/repo/UserRepo.java @@ -1,11 +1,11 @@ -/* - * AppUserRepo.java - * - * Version: - * $Id$ - * - * Revisions: - * $Log$ +/* + * AppUserRepo.java + * + * Version: + * $Id$ + * + * Revisions: + * $Log$ */ package edu.tamu.cap.model.repo; @@ -17,11 +17,11 @@ /** * User repository. - * + * * @author * */ @Repository public interface UserRepo extends AbstractWeaverUserRepo, UserRepoCustom { - + public User findByEmail(String email); } diff --git a/src/main/java/edu/tamu/cap/model/repo/custom/UserRepoCustom.java b/src/main/java/edu/tamu/cap/model/repo/custom/UserRepoCustom.java index 1cf5f310..e46875cc 100644 --- a/src/main/java/edu/tamu/cap/model/repo/custom/UserRepoCustom.java +++ b/src/main/java/edu/tamu/cap/model/repo/custom/UserRepoCustom.java @@ -4,7 +4,7 @@ /** * Custom user repository interface. - * + * * @author * */ @@ -12,7 +12,7 @@ public interface UserRepoCustom { /** * Creates application user based on uin in the repository - * + * * @param uin * String */ @@ -20,4 +20,6 @@ public interface UserRepoCustom { public User create(String uin, String firstName, String lastName, String role); + public User create(String email, String firstName, String lastName, String role, String password); + } diff --git a/src/main/java/edu/tamu/cap/model/repo/impl/UserRepoImpl.java b/src/main/java/edu/tamu/cap/model/repo/impl/UserRepoImpl.java index 95e1279a..0105308c 100644 --- a/src/main/java/edu/tamu/cap/model/repo/impl/UserRepoImpl.java +++ b/src/main/java/edu/tamu/cap/model/repo/impl/UserRepoImpl.java @@ -1,11 +1,11 @@ -/* - * UserRepoImpl.java - * - * Version: - * $Id$ - * - * Revisions: - * $Log$ +/* + * UserRepoImpl.java + * + * Version: + * $Id$ + * + * Revisions: + * $Log$ */ package edu.tamu.cap.model.repo.impl; @@ -20,7 +20,7 @@ /** * Implementaiton of the user repository. - * + * * @author * */ @@ -31,10 +31,10 @@ public class UserRepoImpl extends AbstractWeaverRepoImpl impleme /** * Creates application user in the user repository - * + * * @param uin * Long - * + * * @see edu.tamu.app.model.repo.custom.UserRepoCustom#create(java.lang.Long) */ @Override @@ -52,6 +52,13 @@ public synchronized User create(String uin, String firstName, String lastName, S return user.isPresent() ? user.get() : userRepo.save(new User(uin, firstName, lastName, role)); } + @Override + public synchronized User create(String email, String firstName, String lastName, String role, String password) { + Optional user = userRepo.findByUsername(email); + return user.isPresent() ? user.get() : userRepo.save(new User(email, firstName, lastName, role, password)); +// return userRepo.save(new User(email, firstName, lastName, password, role)); + } + /** * {@inheritDoc} */ diff --git a/src/main/java/edu/tamu/cap/service/EmailTemplateService.java b/src/main/java/edu/tamu/cap/service/EmailTemplateService.java new file mode 100644 index 00000000..b38c05a0 --- /dev/null +++ b/src/main/java/edu/tamu/cap/service/EmailTemplateService.java @@ -0,0 +1,51 @@ +package edu.tamu.cap.service; + +import java.io.IOException; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +import edu.tamu.cap.model.EmailTemplate; + +@Service +public class EmailTemplateService { + @Autowired + FileService fileService; + + @Autowired + private ObjectMapper objectMapper; + + public EmailTemplate getEmailTemplate(String templateName) throws IOException { + EmailTemplate emailTemplate = objectMapper.readValue(fileService.getFileFromResource("classpath:/config/emails/" + templateName+".json"), new TypeReference() {}); + return emailTemplate; + } + + public EmailTemplate buildEmail(String templateName, Map parameters) { + EmailTemplate emailTemplate = null; + try { + emailTemplate = getEmailTemplate(templateName); + } catch (IOException e) { + e.printStackTrace(); + } + return templateParameters(emailTemplate,parameters); + + } + + public EmailTemplate templateParameters(EmailTemplate emailTemplate, Map parameters) { + emailTemplate.setSubject(templateEmailSection(emailTemplate.getSubject(),parameters)); + emailTemplate.setMessage(templateEmailSection(emailTemplate.getMessage(),parameters)); + return emailTemplate; + } + + protected String templateEmailSection(String emailSection, Map parameters) { + for (String name : parameters.keySet()) { + emailSection = emailSection.replaceAll("\\{" + name + "\\}", parameters.get(name)); + } + return emailSection; + } + +} diff --git a/src/main/java/edu/tamu/cap/service/FileService.java b/src/main/java/edu/tamu/cap/service/FileService.java new file mode 100644 index 00000000..5135eac4 --- /dev/null +++ b/src/main/java/edu/tamu/cap/service/FileService.java @@ -0,0 +1,33 @@ +package edu.tamu.cap.service; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.commons.io.IOUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.stereotype.Service; + +@Service +public class FileService { + @Autowired + private ResourcePatternResolver resourcePatternResolver; + + public File getFileFromResource(String templateName) throws IOException { + Resource resource = resourcePatternResolver.getResource("classpath:/config/emails/" + templateName+".json"); + if (resource.getURI().getScheme().equals("jar")) { + return createTempFileFromStream(resource.getInputStream()); + } + return resource.getFile(); + } + + private File createTempFileFromStream(InputStream stream) throws IOException { + File tempFile = File.createTempFile("resource", ".tmp"); + tempFile.deleteOnExit(); + IOUtils.copy(stream, new FileOutputStream(tempFile)); + return tempFile; + } +} diff --git a/src/main/resources/config/emails/user_registration.json b/src/main/resources/config/emails/user_registration.json new file mode 100644 index 00000000..bd38b3ab --- /dev/null +++ b/src/main/resources/config/emails/user_registration.json @@ -0,0 +1,7 @@ +{ + "name": "Registration Email", + "subject": "Cap Account Registration", + "message": "To complete registration of your Cap account, please click the link + below:\n\n\{\{REGISTRATION_URL\}\}" + } +