diff --git a/starter/cloudstorage/.gitignore b/starter/cloudstorage/.gitignore index 65e9ea5ed..b0b86864f 100644 --- a/starter/cloudstorage/.gitignore +++ b/starter/cloudstorage/.gitignore @@ -14,7 +14,7 @@ target/ .sts4-cache ### IntelliJ IDEA ### -.idea +.idea/ *.iws *.iml *.ipr diff --git a/starter/cloudstorage/pom.xml b/starter/cloudstorage/pom.xml index dd561775b..ee744253a 100644 --- a/starter/cloudstorage/pom.xml +++ b/starter/cloudstorage/pom.xml @@ -36,7 +36,11 @@ mybatis-spring-boot-starter 2.1.1 - + + com.mysql + mysql-connector-j + 8.1.0 + org.springframework.boot spring-boot-devtools @@ -86,7 +90,7 @@ io.github.bonigarcia webdrivermanager - 3.8.1 + 5.5.0 test @@ -96,7 +100,13 @@ org.springframework.boot spring-boot-maven-plugin + + + error + + + diff --git a/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/controllers/ErrController.java b/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/controllers/ErrController.java new file mode 100644 index 000000000..f18e813a7 --- /dev/null +++ b/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/controllers/ErrController.java @@ -0,0 +1,19 @@ +package com.udacity.jwdnd.course1.cloudstorage.controllers; + +import org.springframework.boot.web.servlet.error.ErrorController; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +public class ErrController implements ErrorController { + + @RequestMapping("/error") + public String handleError() { + return "error"; + } + + @Override + public String getErrorPath() { + return "/error"; + } +} \ No newline at end of file diff --git a/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/controllers/FileController.java b/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/controllers/FileController.java new file mode 100644 index 000000000..43afee7c3 --- /dev/null +++ b/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/controllers/FileController.java @@ -0,0 +1,81 @@ +package com.udacity.jwdnd.course1.cloudstorage.controllers; + +import com.udacity.jwdnd.course1.cloudstorage.models.File; +import com.udacity.jwdnd.course1.cloudstorage.models.User; +import com.udacity.jwdnd.course1.cloudstorage.services.FileService; +import com.udacity.jwdnd.course1.cloudstorage.services.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; + +import java.io.IOException; + +@Controller +public class FileController { + + private final FileService fileService; + private final UserService userService; + + @Autowired + public FileController(FileService fileService, UserService userService) { + this.fileService = fileService; + this.userService = userService; + } + + + @PostMapping("/fileUpload") + public String fileUpload(@RequestParam("fileUpload") MultipartFile file, Model model) { + try { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String username = (String) authentication.getPrincipal(); + User user = userService.getUserByUsername(username); + Long userId = user.getId(); + fileService.saveFile(file, userId); + } catch (IllegalArgumentException e) { + model.addAttribute("errorMessage", e.getMessage()); + } catch (IOException e) { + model.addAttribute("errorMessage", "Could not parse file."); + } catch (Exception e) { + model.addAttribute("errorMessage", e.getMessage()); + } + return "redirect:/home"; + } + + @GetMapping("/files/{id}") + public ResponseEntity viewFile(@PathVariable("id") Integer fileId) { + // Retrieve the file from the service + File file = fileService.getFileById(fileId); + + if (file == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + + HttpHeaders headers = new HttpHeaders(); + headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getFilename() + "\""); + headers.add(HttpHeaders.CONTENT_TYPE, file.getContenttype()); + + return new ResponseEntity<>(file.getFiledata(), headers, HttpStatus.OK); + } + @GetMapping("/files/delete/{id}") + public String deleteFile(@PathVariable("id") Integer fileId, RedirectAttributes redirectAttributes) { + try { + fileService.deleteFileById(fileId); + redirectAttributes.addFlashAttribute("successMessage", "File deleted successfully."); + } catch (Exception e) { + redirectAttributes.addFlashAttribute("errorMessage", "An error occurred while deleting the file: " + e.getMessage()); + } + + return "redirect:/home"; + } +} diff --git a/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/controllers/HomeController.java b/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/controllers/HomeController.java new file mode 100644 index 000000000..256affc83 --- /dev/null +++ b/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/controllers/HomeController.java @@ -0,0 +1,48 @@ +package com.udacity.jwdnd.course1.cloudstorage.controllers; + +import com.udacity.jwdnd.course1.cloudstorage.models.File; +import com.udacity.jwdnd.course1.cloudstorage.models.User; +import com.udacity.jwdnd.course1.cloudstorage.services.FileService; +import com.udacity.jwdnd.course1.cloudstorage.services.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; + +import java.io.IOException; +import java.util.List; + +@Controller +public class HomeController { + + private final FileService fileService; + private final UserService userService; + + @Autowired + public HomeController(FileService fileService, UserService userService) { + this.fileService = fileService; + this.userService = userService; + } + + @GetMapping("/home") + public String home(Model model) { + + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String username = (String) authentication.getPrincipal(); + User user = userService.getUserByUsername(username); + Long userId = user.getId(); + + List files = null; + try { + files = fileService.getAllFiles(userId); + } catch (IOException e) { + model.addAttribute("errorMessage", "Could not parse file."); + } + + model.addAttribute("files", files); + + return "home"; + } +} diff --git a/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/controllers/NoteController.java b/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/controllers/NoteController.java new file mode 100644 index 000000000..2aa4d91be --- /dev/null +++ b/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/controllers/NoteController.java @@ -0,0 +1,69 @@ +package com.udacity.jwdnd.course1.cloudstorage.controllers; + +import com.udacity.jwdnd.course1.cloudstorage.models.Note; +import com.udacity.jwdnd.course1.cloudstorage.services.NoteService; +import com.udacity.jwdnd.course1.cloudstorage.services.UserService; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +import java.util.logging.Logger; + +@Controller +@RequestMapping("/notes") +public class NoteController { + + private final NoteService noteService; + private final UserService userService; + + @Autowired + public NoteController(NoteService noteService, UserService userService) { + this.noteService = noteService; + this.userService = userService; + } + + @PostMapping("/add") + public String addNote(@ModelAttribute Note note, Authentication authentication, Model model) { + String username = authentication.getName(); + Long userId = userService.getUserByUsername(username).getId(); + note.setUserid(userId.intValue()); + + try { + noteService.addNote(note); + model.addAttribute("successMessage", "Note successfully created!"); + } catch (Exception e) { + model.addAttribute("errorMessage", "There was an error creating the note. Please try again."); + } + return "redirect:/home"; + } + + @PostMapping("/update") + public String updateNote(@ModelAttribute Note note, Authentication authentication, Model model) { + String username = authentication.getName(); + try { + noteService.updateNote(note); + model.addAttribute("successMessage", "Note successfully updated!"); + } catch (Exception e) { + model.addAttribute("errorMessage", "There was an error updating the note. Please try again."); + } + return "redirect:/home"; + } + + @GetMapping("/delete/{noteId}") + public String deleteNote(@PathVariable Integer noteId, Model model) { + try { + noteService.deleteNote(noteId); + model.addAttribute("successMessage", "Note successfully deleted!"); + } catch (Exception e) { + model.addAttribute("errorMessage", "There was an error deleting the note. Please try again."); + } + return "redirect:/home"; + } +} diff --git a/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/controllers/UserController.java b/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/controllers/UserController.java new file mode 100644 index 000000000..546dcfda7 --- /dev/null +++ b/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/controllers/UserController.java @@ -0,0 +1,45 @@ +package com.udacity.jwdnd.course1.cloudstorage.controllers; + +import com.udacity.jwdnd.course1.cloudstorage.mappers.UserMapper; +import com.udacity.jwdnd.course1.cloudstorage.models.User; +import com.udacity.jwdnd.course1.cloudstorage.services.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; + +@Controller +public class UserController { + + private final UserService userService; + + @Autowired + public UserController(UserService userService) { + this.userService = userService; + } + + @GetMapping("/login") + public String loginGet() { + return "login"; + } + + @GetMapping("/signup") + public String signup() { + return "signup"; + } + + @PostMapping("/signup") + public String signupPost(User user, RedirectAttributes redirectAttributes, Model model) { + try { + userService.addUser(user); + redirectAttributes.addFlashAttribute("successMessage", "You successfully signed up!"); + return "redirect:/login"; + } catch (IllegalArgumentException e) { + model.addAttribute("errorMessage", e.getMessage()); + return "signup"; + } + } + +} \ No newline at end of file diff --git a/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/mappers/FileMapper.java b/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/mappers/FileMapper.java new file mode 100644 index 000000000..30b9fca2f --- /dev/null +++ b/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/mappers/FileMapper.java @@ -0,0 +1,31 @@ +package com.udacity.jwdnd.course1.cloudstorage.mappers; + +import com.udacity.jwdnd.course1.cloudstorage.models.File; +import org.apache.ibatis.annotations.Delete; +import org.apache.ibatis.annotations.Insert; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Options; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +@Mapper +public interface FileMapper { + + @Insert("INSERT INTO files (filename, contenttype, filesize, userid, filedata) " + + "VALUES (#{filename}, #{contenttype}, #{filesize}, #{userid}, #{filedata})") + @Options(useGeneratedKeys = true, keyProperty = "fileId") + void insertFile(File file); + + @Select("SELECT * FROM files WHERE fileId = #{fileId}") + File getFileById(Integer fileId); + + @Select("SELECT * FROM files WHERE userid = #{userId}") + List getFilesByUserId(Integer userId); + + @Select("SELECT * FROM files WHERE userid = #{userId} AND filename = #{filename}") + File getFileByUserIdAndFileName(Integer userId, String filename); + + @Delete("DELETE FROM files WHERE fileId = #{fileId}") + void deleteFileById(Integer fileId); +} \ No newline at end of file diff --git a/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/mappers/NoteMapper.java b/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/mappers/NoteMapper.java new file mode 100644 index 000000000..ab267c634 --- /dev/null +++ b/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/mappers/NoteMapper.java @@ -0,0 +1,29 @@ +package com.udacity.jwdnd.course1.cloudstorage.mappers; + +import com.udacity.jwdnd.course1.cloudstorage.models.Note; +import org.apache.ibatis.annotations.Delete; +import org.apache.ibatis.annotations.Insert; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Options; +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.Update; + +@Mapper +public interface NoteMapper { + + @Insert("INSERT INTO NOTES (notetitle, notedescription, userid) VALUES (#{notetitle}, #{notedescription}, #{userid})") + @Options(useGeneratedKeys = true, keyProperty = "noteId") + int insertNote(Note note); + + @Select("SELECT * FROM notes WHERE noteid = #{noteId}") + Note getNoteById(Integer noteId); + + @Select("SELECT * FROM notes WHERE userid = #{userid} AND notetitle = #{notetitle}") + Note getNoteByUserIdAndTitle(Integer userid, String notetitle); // Updated method name for clarity + + @Delete("DELETE FROM NOTES WHERE noteid = #{noteId}") + int deleteNote(Integer noteId); + + @Update("UPDATE NOTES SET notetitle = #{notetitle}, notedescription = #{notedescription} WHERE noteid = #{noteId}") + int updateNote(Note note); +} diff --git a/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/mappers/UserMapper.java b/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/mappers/UserMapper.java new file mode 100644 index 000000000..6a1976573 --- /dev/null +++ b/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/mappers/UserMapper.java @@ -0,0 +1,37 @@ +package com.udacity.jwdnd.course1.cloudstorage.mappers; + +import com.udacity.jwdnd.course1.cloudstorage.models.User; +import org.apache.ibatis.annotations.Delete; +import org.apache.ibatis.annotations.Insert; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Options; +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.Update; + +import java.util.List; + +@Mapper +public interface UserMapper { + + @Select("SELECT * FROM users WHERE id = #{id}") + User selectUserById(Long id); + + @Select("SELECT * FROM users WHERE username = #{username}") + User selectUserByUsername(String username); + + @Insert("INSERT INTO users (username, salt, password, firstname, lastname) VALUES (#{username}, #{salt}, #{password}, #{firstname}, #{lastname})") + @Options(useGeneratedKeys = true, keyProperty = "id") + void insertUser(User user); + + @Delete("DELETE FROM users WHERE id = #{id}") + void deleteUserById(Long id); + + @Update("UPDATE users SET username = #{username}, email = #{email}, salt = #{salt}, password = #{password} WHERE id = #{id}") + void updateUser(User user); + + @Select("SELECT * FROM users") + List selectAllUsers(); + + @Delete("DELETE FROM users") + void deleteAllUsers(); +} diff --git a/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/models/File.java b/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/models/File.java new file mode 100644 index 000000000..ae96a0459 --- /dev/null +++ b/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/models/File.java @@ -0,0 +1,58 @@ +package com.udacity.jwdnd.course1.cloudstorage.models; + +public class File { + private Integer fileId; + private String filename; + private String contenttype; + private String filesize; + private Integer userid; + private byte[] filedata; + + public Integer getFileId() { + return fileId; + } + + public void setFileId(Integer fileId) { + this.fileId = fileId; + } + + public String getFilename() { + return filename; + } + + public void setFilename(String filename) { + this.filename = filename; + } + + public String getContenttype() { + return contenttype; + } + + public void setContenttype(String contenttype) { + this.contenttype = contenttype; + } + + public String getFilesize() { + return filesize; + } + + public void setFilesize(String filesize) { + this.filesize = filesize; + } + + public Integer getUserid() { + return userid; + } + + public void setUserid(Integer userid) { + this.userid = userid; + } + + public byte[] getFiledata() { + return filedata; + } + + public void setFiledata(byte[] filedata) { + this.filedata = filedata; + } +} diff --git a/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/models/Note.java b/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/models/Note.java new file mode 100644 index 000000000..8e186c813 --- /dev/null +++ b/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/models/Note.java @@ -0,0 +1,44 @@ +package com.udacity.jwdnd.course1.cloudstorage.models; + +public class Note { + + public Integer getNoteId() { + return noteId; + } + + public void setNoteId(Integer noteId) { + this.noteId = noteId; + } + + public String getNotetitle() { + return notetitle; + } + + public void setNotetitle(String notetitle) { + this.notetitle = notetitle; + } + + public String getNotedescription() { + return notedescription; + } + + public void setNotedescription(String notedescription) { + this.notedescription = notedescription; + } + + public Integer getUserid() { + return userid; + } + + public void setUserid(Integer userid) { + this.userid = userid; + } + + private Integer noteId; + + private String notetitle; + + private String notedescription; + + private Integer userid; +} diff --git a/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/models/User.java b/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/models/User.java new file mode 100644 index 000000000..9f47dc521 --- /dev/null +++ b/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/models/User.java @@ -0,0 +1,93 @@ +package com.udacity.jwdnd.course1.cloudstorage.models; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; +import java.util.Collections; + +public class User implements UserDetails { + private Long userid; + private String username; + private String firstname; + private String lastname; + private String salt; + private String password; + + public String getFirstname() { + return firstname; + } + + public void setFirstname(String firstname) { + this.firstname = firstname; + } + + public String getLastname() { + return lastname; + } + + public void setLastname(String lastname) { + this.lastname = lastname; + } + + public String getSalt() { + return salt; + } + + public void setSalt(String salt) { + this.salt = salt; + } + + @Override + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Long getId() { + return userid; + } + + public void setId(Long id) { + this.userid = id; + } + + @Override + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + @Override + public Collection getAuthorities() { + return Collections.emptyList(); + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } + +} + diff --git a/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/security/AuthProvider.java b/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/security/AuthProvider.java new file mode 100644 index 000000000..f494f1b42 --- /dev/null +++ b/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/security/AuthProvider.java @@ -0,0 +1,39 @@ +package com.udacity.jwdnd.course1.cloudstorage.security; + +import com.udacity.jwdnd.course1.cloudstorage.services.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.stereotype.Component; + +import java.util.Collections; + +@Component +public class AuthProvider implements AuthenticationProvider { + + private final UserService userService; + + @Autowired + public AuthProvider(UserService userService) { + this.userService = userService; + } + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + String username = authentication.getName(); + String password = authentication.getCredentials().toString(); + + try { + userService.validateUser(username, password); + } catch (IllegalArgumentException e) { + throw new AuthenticationException("Invalid username or password") {}; + } + + return new UsernamePasswordAuthenticationToken(username, password, Collections.emptyList()); + } + + @Override + public boolean supports(Class authentication) { + return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication); + } +} \ No newline at end of file diff --git a/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/security/SecurityConfig.java b/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/security/SecurityConfig.java new file mode 100644 index 000000000..0cfd1a153 --- /dev/null +++ b/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/security/SecurityConfig.java @@ -0,0 +1,45 @@ +package com.udacity.jwdnd.course1.cloudstorage.security; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +@Configuration +@EnableWebSecurity +public class SecurityConfig extends WebSecurityConfigurerAdapter { + + private final AuthProvider authProvider; + + @Autowired + public SecurityConfig(AuthProvider authProvider) { + this.authProvider = authProvider; + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.authenticationProvider(authProvider); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeRequests() + .antMatchers("/signup", "/login", "/css/**", "/js/**").permitAll() + .antMatchers("/fileUpload").authenticated() // Make sure this is correct + .anyRequest().authenticated() + .and() + .formLogin() + .loginPage("/login") + .defaultSuccessUrl("/home", true) + .permitAll() + .and() + .logout() + .logoutUrl("/logout") + .logoutSuccessUrl("/login?logout") + .permitAll(); + } + +} \ No newline at end of file diff --git a/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/services/EncryptionService.java b/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/services/EncryptionService.java index e2dced95a..abf9dba4e 100644 --- a/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/services/EncryptionService.java +++ b/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/services/EncryptionService.java @@ -2,7 +2,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import javax.crypto.*; @@ -15,17 +14,20 @@ @Service public class EncryptionService { private Logger logger = LoggerFactory.getLogger(EncryptionService.class); + private Cipher cipher; + + EncryptionService() throws NoSuchPaddingException, NoSuchAlgorithmException { + this.cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); + } public String encryptValue(String data, String key) { byte[] encryptedValue = null; try { - Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); SecretKey secretKey = new SecretKeySpec(key.getBytes(), "AES"); cipher.init(Cipher.ENCRYPT_MODE, secretKey); encryptedValue = cipher.doFinal(data.getBytes("UTF-8")); - } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException - | UnsupportedEncodingException | IllegalBlockSizeException | BadPaddingException e) { + } catch (InvalidKeyException | UnsupportedEncodingException | IllegalBlockSizeException | BadPaddingException e) { logger.error(e.getMessage()); } @@ -36,12 +38,10 @@ public String decryptValue(String data, String key) { byte[] decryptedValue = null; try { - Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); SecretKey secretKey = new SecretKeySpec(key.getBytes(), "AES"); cipher.init(Cipher.DECRYPT_MODE, secretKey); decryptedValue = cipher.doFinal(Base64.getDecoder().decode(data)); - } catch (NoSuchAlgorithmException | NoSuchPaddingException - | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) { + } catch (InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) { logger.error(e.getMessage()); } diff --git a/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/services/FileService.java b/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/services/FileService.java new file mode 100644 index 000000000..d5637f335 --- /dev/null +++ b/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/services/FileService.java @@ -0,0 +1,62 @@ +package com.udacity.jwdnd.course1.cloudstorage.services; + +import com.udacity.jwdnd.course1.cloudstorage.mappers.FileMapper; +import com.udacity.jwdnd.course1.cloudstorage.models.File; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.List; + +@Service +public class FileService { + + private final FileMapper fileMapper; + + @Autowired + public FileService(FileMapper fileMapper) { + this.fileMapper = fileMapper; + } + + public void saveFile(MultipartFile multipartFile, long userId) throws IOException { + File file = new com.udacity.jwdnd.course1.cloudstorage.models.File(); + + if (userId < Integer.MIN_VALUE || userId > Integer.MAX_VALUE) { + throw new IllegalArgumentException("Could not convert userid." + userId); + } + Integer id = (int) userId; + + File found = fileMapper.getFileByUserIdAndFileName(id, multipartFile.getOriginalFilename()); + + if (found == null) { + throw new IllegalArgumentException("A file with the same name already exists for this user."); + } + + file.setFilename(multipartFile.getOriginalFilename()); + file.setContenttype(multipartFile.getContentType()); + file.setFilesize(String.valueOf(multipartFile.getSize())); + + file.setUserid(id); + file.setFiledata(multipartFile.getBytes()); + + fileMapper.insertFile(file); + } + + public File getFileById(Integer id) { + return fileMapper.getFileById(id); + } + + public void deleteFileById(Integer id) { + fileMapper.deleteFileById(id); + } + + public List getAllFiles(long userId) throws IOException { + if (userId < Integer.MIN_VALUE || userId > Integer.MAX_VALUE) { + throw new IllegalArgumentException("Could not convert userid." + userId); + } + Integer id = (int) userId; + + return fileMapper.getFilesByUserId(id); + } +} diff --git a/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/services/HashService.java b/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/services/HashService.java index e6d907b08..9abea89c5 100644 --- a/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/services/HashService.java +++ b/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/services/HashService.java @@ -2,7 +2,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; @@ -20,8 +20,9 @@ public String getHashedValue(String data, String salt) { byte[] hashedValue = null; int iterCount = 12288; - int derivedKeyLength = 256; + int derivedKeyLength = 128; // Reduce to 128 bits (16 bytes) KeySpec spec = new PBEKeySpec(data.toCharArray(), salt.getBytes(), iterCount, derivedKeyLength * 8); + try { SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); hashedValue = factory.generateSecret(spec).getEncoded(); diff --git a/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/services/NoteService.java b/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/services/NoteService.java new file mode 100644 index 000000000..b4fcba65c --- /dev/null +++ b/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/services/NoteService.java @@ -0,0 +1,41 @@ +package com.udacity.jwdnd.course1.cloudstorage.services; + +import com.udacity.jwdnd.course1.cloudstorage.mappers.NoteMapper; +import com.udacity.jwdnd.course1.cloudstorage.models.Note; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + + + +@Service +public class NoteService { + + private final NoteMapper noteMapper; + + @Autowired + public NoteService(NoteMapper noteMapper) { + this.noteMapper = noteMapper; + } + + public void addNote(Note note) { + Note found = noteMapper.getNoteByUserIdAndTitle(note.getUserid(), note.getNotetitle()); + if (found != null) { + throw new IllegalArgumentException("A note with the same name already exists for this user."); + } else { + noteMapper.insertNote(note); + } + } + + public void updateNote(Note note) { + Note found = noteMapper.getNoteByUserIdAndTitle(note.getUserid(), note.getNotetitle()); + if (found == null) { + throw new IllegalArgumentException("This note does not exist."); + } else { + noteMapper.updateNote(note); + } + } + + public void deleteNote(Integer noteId) { + noteMapper.deleteNote(noteId); + } +} diff --git a/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/services/UserService.java b/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/services/UserService.java new file mode 100644 index 000000000..a3ddd178b --- /dev/null +++ b/starter/cloudstorage/src/main/java/com/udacity/jwdnd/course1/cloudstorage/services/UserService.java @@ -0,0 +1,78 @@ +package com.udacity.jwdnd.course1.cloudstorage.services; + +import com.udacity.jwdnd.course1.cloudstorage.mappers.UserMapper; +import com.udacity.jwdnd.course1.cloudstorage.models.User; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.security.SecureRandom; +import java.util.Base64; + +@Service +public class UserService { + private final UserMapper userMapper; + private final HashService hashService; + + private final EncryptionService encryptionService; + + @Autowired + public UserService(UserMapper userMapper, HashService hashService, EncryptionService encryptionService) { + this.userMapper = userMapper; + this.hashService = hashService; + this.encryptionService = encryptionService; + } + + public void addUser(User user) { + User existingUser = userMapper.selectUserByUsername(user.getUsername()); + if (existingUser != null) { + throw new IllegalArgumentException("Username already exists. Please choose a different username."); + } + SecureRandom random = new SecureRandom(); + byte[] salt = new byte[16]; + random.nextBytes(salt); + String encodedSalt = Base64.getEncoder().encodeToString(salt); + String hashedPassword = hashService.getHashedValue(user.getPassword(), encodedSalt); + if (hashedPassword.length() > 255) { + throw new IllegalArgumentException("Hashed password exceeds the maximum allowed length of 255 characters."); + } + user.setSalt(encodedSalt); + user.setPassword(hashedPassword); + userMapper.insertUser(user); + } + + public void validateUser(User user) { + User found = userMapper.selectUserByUsername(user.getUsername()); + if (found == null) { + throw new IllegalArgumentException("User not found"); + } + + String storedSalt = user.getSalt(); + String storedHashedPassword = user.getPassword(); + + String hashedInputPassword = hashService.getHashedValue(user.getPassword(), storedSalt); + + if (!hashedInputPassword.equals(storedHashedPassword)) { + throw new IllegalArgumentException("Invalid username or password"); + } + } + + public void validateUser(String username, String password) { + User user = userMapper.selectUserByUsername(username); + if (user == null) { + throw new IllegalArgumentException("User not found"); + } + + String storedSalt = user.getSalt(); + String storedHashedPassword = user.getPassword(); + + String hashedInputPassword = hashService.getHashedValue(password, storedSalt); + + if (!hashedInputPassword.equals(storedHashedPassword)) { + throw new IllegalArgumentException("Invalid username or password"); + } + } + + public User getUserByUsername(String username) { + return userMapper.selectUserByUsername(username); + } +} diff --git a/starter/cloudstorage/src/main/resources/application.properties b/starter/cloudstorage/src/main/resources/application.properties index 8b1378917..ed00d9526 100644 --- a/starter/cloudstorage/src/main/resources/application.properties +++ b/starter/cloudstorage/src/main/resources/application.properties @@ -1 +1,9 @@ - +logging.level.org.springframework.security=DEBUG +logging.level.org.thymeleaf=DEBUG +mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl +spring.datasource.url=jdbc:mysql://localhost:3306/project?useSSL=false&serverTimezone=UTC +spring.datasource.username=root +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +spring.servlet.multipart.max-file-size=10MB +spring.servlet.multipart.max-request-size=10MB +logging.level.org.springframework=DEBUG \ No newline at end of file diff --git a/starter/cloudstorage/src/main/resources/templates/error.html b/starter/cloudstorage/src/main/resources/templates/error.html new file mode 100644 index 000000000..cd974b0de --- /dev/null +++ b/starter/cloudstorage/src/main/resources/templates/error.html @@ -0,0 +1,46 @@ + + + + + + Page Not Found + + + + +
+
404
+
Oops! The page you are looking for does not exist.
+ Return to Home Page +
+ + diff --git a/starter/cloudstorage/src/main/resources/templates/home.html b/starter/cloudstorage/src/main/resources/templates/home.html index b2f072dc6..d52e7a1f3 100644 --- a/starter/cloudstorage/src/main/resources/templates/home.html +++ b/starter/cloudstorage/src/main/resources/templates/home.html @@ -11,10 +11,15 @@
-
+
+ + +