Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SDR returns 400 bad request when deserializing a linked entity where access is denied instead of a 403 forbidden #2334

Open
burnumd opened this issue Nov 17, 2023 · 0 comments
Assignees
Labels
status: waiting-for-triage An issue we've not yet triaged

Comments

@burnumd
Copy link

burnumd commented Nov 17, 2023

When creating an entity that contains a linked resource whose repository's find one method is restricted, SDR throws an HttpMessageNotReadable exception during deserialization of the linked resource (so it can't be changed using @ControllerAdvice exception handlers, resulting in the end-user receiving a 400 bad request response on a well-formed and valid request instead of the expected 403 forbidden status one would receive if they failed the global method security on any other kind of request. The following example code demonstrates the behavior by performing a POST to /parents with the body { "id":"1" } and then a POST to /children with the body { "id": "child1", "parent": "/parents/1" }.

Security Configuration: in-memory user user with password password and basic auth configured

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

    @Bean
    public UserDetailsService users() {
        UserDetails user = User.builder()
                .username("user")
                .password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
                .roles("USER")
                .build();
        return new InMemoryUserDetailsManager(user);
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf().disable().authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated());
        http.httpBasic(withDefaults());
        return http.build();
    }

}

Parent domain object

@Entity
@Data
@Table(name = "parent_t")
public class Parent {

    @Id
    private String id;
    @OneToMany
    private List<Child> children;

}

Parent repository with method security

public interface ParentRepository extends CrudRepository<Parent, String> {

    @Override
    @PreAuthorize("isAuthenticated()")
    @PostAuthorize("@authoritiesUtil.canViewParent(returnObject)")
    Optional<Parent> findById(String id);

    @Override
    @PreAuthorize("isAuthenticated()")
    <S extends Parent> S save(S entity);

}

Child domain object

@Entity
@Data
@Table(name = "child_t")
public class Child {

    @Id
    private String id;
    @ManyToOne(optional = false)
    private Parent parent;

}

Child repository with method security:

public interface ChildRepository extends CrudRepository<Child, String> {

    @Override
    @PreAuthorize("isAuthenticated()")
    <S extends Child> S save(S entity);

}

Authorities utility class referenced in parent repository:

@Component
public class AuthoritiesUtil {

    public boolean canViewParent(Optional<Parent> parent) {
        return false; //insert logic to allow this in some cases
    }

}

Application class

@SpringBootApplication
@EnableMethodSecurity
public class SpringDataDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringDataDemoApplication.class, args);
    }

}

POM

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.1.5</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>
    <groupId>edu.iu.es.ep.demo</groupId>
    <artifactId>spring-data-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-data-demo</name>
    <description>spring-data-demo</description>

    <dependencies>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-rest</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-rest-hal-explorer</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <image>
                        <builder>paketobuildpacks/builder-jammy-base:latest</builder>
                    </image>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

In this case, I would expect to receive a 403 forbidden status instead of a 400 bad request with the message JSON parse error: Access Denied because the request is well-formatted and the linked object exists, but the user is forbidden from using it. Please let me know if you have any questions or concerns. Thank you.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Nov 17, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: waiting-for-triage An issue we've not yet triaged
Projects
None yet
Development

No branches or pull requests

3 participants