Skip to content

Commit

Permalink
add authorization
Browse files Browse the repository at this point in the history
  • Loading branch information
Maksim Litvinov committed Nov 15, 2023
1 parent 2669a99 commit d067ed6
Show file tree
Hide file tree
Showing 9 changed files with 102 additions and 7 deletions.
2 changes: 2 additions & 0 deletions src/main/java/io/hexlet/blog/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
Expand All @@ -21,6 +22,7 @@

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
@Autowired
private JwtDecoder jwtDecoder;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
Expand All @@ -28,6 +29,11 @@
@RestController
@RequestMapping("/api")
public class PostsController {

private static final String ONLY_AUTHOR = """
@postRepository.findById(#id).get().getAuthor().getEmail() == authentication.getName()
""";

@Autowired
private PostRepository repository;

Expand Down Expand Up @@ -71,6 +77,7 @@ PostDTO show(@PathVariable Long id) {

@PutMapping("/posts/{id}")
@ResponseStatus(HttpStatus.OK)
@PreAuthorize(ONLY_AUTHOR)
PostDTO update(@RequestBody @Valid PostUpdateDTO postData, @PathVariable Long id) {
var post = repository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Not Found"));
Expand All @@ -82,6 +89,7 @@ PostDTO update(@RequestBody @Valid PostUpdateDTO postData, @PathVariable Long id

@DeleteMapping("/posts/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
@PreAuthorize(ONLY_AUTHOR)
void destroy(@PathVariable Long id) {
repository.deleteById(id);
}
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/io/hexlet/blog/controller/api/UsersController.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

import java.util.List;

import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
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.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
Expand Down Expand Up @@ -40,6 +42,14 @@ ResponseEntity<List<UserDTO>> index() {
.body(result);
}

@PostMapping("/users")
@ResponseStatus(HttpStatus.CREATED)
UserDTO create(@Valid @RequestBody UserDTO userData) {
var user = userMapper.map(userData);
repository.save(user);
return userMapper.map(user);
}

@PutMapping("/users/{id}")
@ResponseStatus(HttpStatus.OK)
UserDTO update(@RequestBody UserUpdateDTO userData, @PathVariable Long id) {
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/io/hexlet/blog/dto/UserDTO.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
package io.hexlet.blog.dto;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;

@Setter
@Getter
public class UserDTO {
private Long id;

@Email
@NotBlank
private String username;

private String firstName;

private String lastName;

@Size(min = 3, max = 100)
private String password;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException{
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public <T> T unwrap(JsonNullable<T> jsonNullable) {

/**
* Checks whether nullable parameter was passed explicitly.
*
*
* @return true if value was set explicitly, false otherwise
*/
@Condition
Expand Down
29 changes: 25 additions & 4 deletions src/main/java/io/hexlet/blog/mapper/UserMapper.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package io.hexlet.blog.mapper;

import org.mapstruct.BeforeMapping;
import org.mapstruct.InheritInverseConfiguration;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingConstants;
import org.mapstruct.MappingTarget;
import org.mapstruct.NullValuePropertyMappingStrategy;
Expand All @@ -9,18 +12,36 @@
import io.hexlet.blog.dto.UserDTO;
import io.hexlet.blog.dto.UserUpdateDTO;
import io.hexlet.blog.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@Mapper(
uses = { JsonNullableMapper.class, ReferenceMapper.class },
nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE,
componentModel = MappingConstants.ComponentModel.SPRING,
unmappedTargetPolicy = ReportingPolicy.IGNORE
uses = {JsonNullableMapper.class, ReferenceMapper.class},
nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE,
componentModel = MappingConstants.ComponentModel.SPRING,
unmappedTargetPolicy = ReportingPolicy.IGNORE
)
public abstract class UserMapper {

@Autowired
private BCryptPasswordEncoder encoder;

@Mapping(target = "id", ignore = true)
@Mapping(target = "email", source = "username")
@Mapping(target = "passwordDigest", source = "password")
public abstract User map(UserDTO model);

public abstract User map(UserUpdateDTO model);

@InheritInverseConfiguration
@Mapping(target = "password", ignore = true)
public abstract UserDTO map(User model);

public abstract void update(UserUpdateDTO update, @MappingTarget User destination);

@BeforeMapping
public void encryptPassword(UserDTO data) {
var password = data.getPassword();
data.setPassword(encoder.encode(password));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,25 @@ public void testUpdate() throws Exception {
assertThat(testPost.getName()).isEqualTo(data.getName().get());
}

@Test
public void testUpdateFailed() throws Exception {
postRepository.save(testPost);

var data = new PostUpdateDTO();
data.setName(JsonNullable.of("new name"));

var request = put("/api/posts/" + testPost.getId())
.with(jwt())
.contentType(MediaType.APPLICATION_JSON)
.content(om.writeValueAsString(data));

mockMvc.perform(request)
.andExpect(status().isForbidden());

var actualPost = postRepository.findById(testPost.getId()).get();
assertThat(actualPost.getName()).isEqualTo(testPost.getName());
}

@Test
public void testShow() throws Exception {
postRepository.save(testPost);
Expand All @@ -128,10 +147,20 @@ public void testShow() throws Exception {
@Test
public void testDestroy() throws Exception {
postRepository.save(testPost);
var request = delete("/api/posts/" + testPost.getId()).with(jwt());
var request = delete("/api/posts/" + testPost.getId()).with(token);
mockMvc.perform(request)
.andExpect(status().isNoContent());

assertThat(postRepository.existsById(testPost.getId())).isEqualTo(false);
}

@Test
public void testDestroyFailed() throws Exception {
postRepository.save(testPost);
var request = delete("/api/posts/" + testPost.getId()).with(jwt());
mockMvc.perform(request)
.andExpect(status().isForbidden());

assertThat(postRepository.existsById(testPost.getId())).isEqualTo(true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import java.util.HashMap;
Expand Down Expand Up @@ -62,6 +63,19 @@ public void testIndex() throws Exception {
.andExpect(status().isOk());
}

@Test
void testCreate() throws Exception {
var data = Instancio.of(modelGenerator.getUserModel())
.create();

var request = post("/api/users")
.with(token)
.contentType(MediaType.APPLICATION_JSON)
.content(om.writeValueAsString(data));
mockMvc.perform(request)
.andExpect(status().isCreated());
}

@Test
public void testUpdate() throws Exception {

Expand Down

0 comments on commit d067ed6

Please sign in to comment.