package de.asideas.crowdsource.controller; import com.fasterxml.jackson.annotation.JsonView; import de.asideas.crowdsource.domain.exception.ForbiddenException; import de.asideas.crowdsource.domain.exception.InvalidRequestException; import de.asideas.crowdsource.domain.model.UserEntity; import de.asideas.crowdsource.domain.shared.ProjectStatus; import de.asideas.crowdsource.presentation.Pledge; import de.asideas.crowdsource.presentation.project.Attachment; import de.asideas.crowdsource.presentation.project.Project; import de.asideas.crowdsource.presentation.project.ProjectStatusUpdate; import de.asideas.crowdsource.security.Roles; import de.asideas.crowdsource.service.ProjectService; import de.asideas.crowdsource.service.UserService; 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.core.io.InputStreamResource; import org.springframework.http.HttpStatus; import org.springframework.http.InvalidMediaTypeException; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.access.annotation.Secured; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import javax.validation.Valid; import javax.validation.constraints.NotNull; import java.io.IOException; import java.io.InputStream; import java.security.Principal; import java.util.List; import java.util.stream.Collectors; import static de.asideas.crowdsource.domain.shared.ProjectStatus.FULLY_PLEDGED; import static de.asideas.crowdsource.domain.shared.ProjectStatus.PUBLISHED; import static de.asideas.crowdsource.domain.shared.ProjectStatus.PUBLISHED_DEFERRED; @RestController public class ProjectController { private static final Logger log = LoggerFactory.getLogger(ProjectController.class); @Autowired private ProjectService projectService; @Autowired private UserService userService; @Value("#{T(org.springframework.http.MediaType).parseMediaTypes('${de.asideas.crowdsource.attachment.allowedmediatypes}')}") private List<MediaType> attachmentTypesAllowed; @Secured({Roles.ROLE_TRUSTED_ANONYMOUS, Roles.ROLE_USER}) @RequestMapping(value = "/projects", method = RequestMethod.GET) @JsonView(Project.ProjectSummaryView.class) public List<Project> getProjects(Authentication auth) { UserEntity userEntity = userFromAuthentication(auth); final List<Project> projects = projectService.getProjects(userEntity); // filter projects. only return projects that are published, fully pledged or created by the requesting user (or if requestor is admin) return projects.stream().filter(project -> mayViewProjectFilter(project, auth)).collect(Collectors.toList()); } @Secured({Roles.ROLE_TRUSTED_ANONYMOUS, Roles.ROLE_USER}) @RequestMapping(value = "/project/{projectId}", method = RequestMethod.GET) public Project getProject(@PathVariable String projectId, Authentication auth) { UserEntity userEntity = userFromAuthentication(auth); final Project project = projectService.getProject(projectId, userEntity); if (!mayViewProjectFilter(project, auth)) { throw new ForbiddenException(); } return project; } @Secured(Roles.ROLE_USER) @ResponseStatus(HttpStatus.CREATED) @RequestMapping(value = "/project", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE) public Project addProject(@RequestBody @Valid Project project, Principal principal) { UserEntity userEntity = userByPrincipal(principal); return projectService.addProject(project, userEntity); } @Secured(Roles.ROLE_USER) @ResponseStatus(HttpStatus.CREATED) @RequestMapping(value = "/project/{projectId}/pledges", method = RequestMethod.POST) public void pledgeProject(@PathVariable String projectId, @RequestBody @Valid Pledge pledge, Principal principal) { projectService.pledge(projectId, userByPrincipal(principal), pledge); } @Secured(Roles.ROLE_ADMIN) @ResponseStatus(HttpStatus.OK) @RequestMapping(value = "/project/{projectId}/status", method = RequestMethod.PATCH) public Project modifyProjectStatus(@PathVariable("projectId") String projectId, @RequestBody @Valid @NotNull ProjectStatusUpdate newStatus, Principal principal) { return projectService.modifyProjectStatus(projectId, newStatus.status, userByPrincipal(principal)); } @Secured(Roles.ROLE_USER) @ResponseStatus(HttpStatus.OK) @RequestMapping(value = "/project/{projectId}", method = RequestMethod.PUT) public Project modifyProjectMasterdata(@PathVariable("projectId") String projectId, @RequestBody @Valid @NotNull Project modifiedProject, Principal principal) { return projectService.modifyProjectMasterdata(projectId, modifiedProject, userByPrincipal(principal)); } @Secured(Roles.ROLE_USER) @ResponseStatus(HttpStatus.OK) @RequestMapping(value = "/projects/{projectId}/likes", method = RequestMethod.POST) public void likeProject(@PathVariable("projectId") String projectId, Principal principal) { projectService.likeProject(projectId, userByPrincipal(principal)); } @Secured(Roles.ROLE_USER) @ResponseStatus(HttpStatus.OK) @RequestMapping(value = "/projects/{projectId}/likes", method = RequestMethod.DELETE) public void unlikeProject(@PathVariable("projectId") String projectId, Principal principal) { projectService.unlikeProject(projectId, userByPrincipal(principal)); } @Secured(Roles.ROLE_USER) @ResponseStatus(HttpStatus.OK) @RequestMapping(value = "/projects/{projectId}/attachments", method = RequestMethod.POST) public Attachment addProjectAttachment(@PathVariable("projectId") String projectId, @RequestParam("file") MultipartFile file, Principal principal) { if (file.isEmpty()) { throw InvalidRequestException.fileMustNotBeEmpty(); } if (!contentTypeAllowed(file.getContentType())) { throw InvalidRequestException.filetypeNotAllowed(); } try ( InputStream inputStream = file.getInputStream() ) { Attachment attachment = Attachment.asCreationCommand(file.getOriginalFilename(), file.getContentType(), inputStream); return projectService.addProjectAttachment(projectId, attachment, userByPrincipal(principal)); } catch (IOException e) { log.warn("Couldn' process file input, due to stream threw IOException; ProjectId: {}", projectId, e); throw new RuntimeException("Internal error, couldn't process file stream", e); } } @Secured({Roles.ROLE_TRUSTED_ANONYMOUS, Roles.ROLE_USER}) @RequestMapping(value = "/projects/{projectId}/attachments/{fileReference}", method = RequestMethod.GET) public ResponseEntity<InputStreamResource> serveProjectAttachment(@PathVariable("projectId") String projectId, @PathVariable("fileReference") String fileReference) throws IOException { final Attachment attachment = projectService.loadProjectAttachment(projectId, Attachment.asLookupByIdCommand(fileReference)); return ResponseEntity.ok() .contentLength(attachment.getSize()) .contentType(MediaType.valueOf(attachment.getType())) .body(new InputStreamResource(attachment.getPayload())); } @Secured(Roles.ROLE_USER) @ResponseStatus(HttpStatus.NO_CONTENT) @RequestMapping(value = "/projects/{projectId}/attachments/{fileReference}", method = RequestMethod.DELETE) public void deleteProjectAttachment(@PathVariable("projectId") String projectId, @PathVariable("fileReference") String fileReference, Principal principal) { projectService.deleteProjectAttachment(projectId, Attachment.asLookupByIdCommand(fileReference), userByPrincipal(principal)); } private boolean contentTypeAllowed(String contentType) { MediaType mediaType; try { mediaType = MediaType.parseMediaType(contentType); } catch (InvalidMediaTypeException e) { log.warn("Couldn't parse media type {}", contentType, e); return false; } for (MediaType el : attachmentTypesAllowed) { if (el.includes(mediaType)) { return true; } } return false; } private boolean mayViewProjectFilter(Project project, Authentication auth) { // fully pledged and published are always visible final ProjectStatus status = project.getStatus(); if (status == FULLY_PLEDGED || status == PUBLISHED || status == PUBLISHED_DEFERRED) { return true; } if (auth == null) { return false; } // admins may do everything for (GrantedAuthority grantedAuthority : auth.getAuthorities()) { if (Roles.ROLE_ADMIN.equals(grantedAuthority.getAuthority())) { return true; } } // the creator always may see his project return project.getCreator().getEmail().equals(auth.getName()); } private UserEntity userByPrincipal(Principal principal) { return userService.getUserByEmail(principal.getName()); } private UserEntity userFromAuthentication(Authentication auth) { UserEntity userEntity = null; if (auth != null && auth.isAuthenticated()) { if (auth.getAuthorities().contains(new SimpleGrantedAuthority(Roles.ROLE_TRUSTED_ANONYMOUS))) { return null; } userEntity = userService.getUserByEmail(auth.getName()); } return userEntity; } }