SecurityConfig.java

1
package edu.ucsb.cs156.gauchoride.config;
2
3
import java.io.IOException;
4
import java.util.ArrayList;
5
import java.util.HashSet;
6
import java.util.List;
7
import java.util.Map;
8
import java.util.Optional;
9
import java.util.Set;
10
import java.util.function.Supplier;
11
12
import edu.ucsb.cs156.gauchoride.entities.User;
13
import edu.ucsb.cs156.gauchoride.repositories.UserRepository;
14
import jakarta.servlet.FilterChain;
15
import jakarta.servlet.ServletException;
16
import jakarta.servlet.http.HttpServletRequest;
17
import jakarta.servlet.http.HttpServletResponse;
18
import lombok.extern.slf4j.Slf4j;
19
20
import org.springframework.beans.factory.annotation.Autowired;
21
import org.springframework.beans.factory.annotation.Value;
22
import org.springframework.context.annotation.Bean;
23
import org.springframework.context.annotation.Configuration;
24
import org.springframework.security.config.Customizer;
25
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
26
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
27
import org.springframework.security.config.annotation.web.builders.WebSecurity;
28
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
29
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
30
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
31
import org.springframework.security.config.http.SessionCreationPolicy;
32
import org.springframework.security.core.Authentication;
33
import org.springframework.security.core.GrantedAuthority;
34
import org.springframework.security.core.authority.SimpleGrantedAuthority;
35
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
36
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
37
import org.springframework.security.web.SecurityFilterChain;
38
import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint;
39
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
40
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
41
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
42
import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
43
import org.springframework.security.web.csrf.CsrfTokenRequestHandler;
44
import org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler;
45
import org.springframework.security.web.csrf.CsrfToken;
46
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
47
48
import org.springframework.util.StringUtils;
49
import org.springframework.web.filter.OncePerRequestFilter;
50
51
@Configuration
52
@EnableWebSecurity
53
@EnableMethodSecurity
54
@Slf4j
55
public class SecurityConfig {
56
57
  @Value("${app.admin.emails}")
58
  private final List<String> adminEmails = new ArrayList<>();
59
60
  @Autowired
61
  UserRepository userRepository;
62
63
  // https://docs.spring.io/spring-security/reference/servlet/exploits/csrf.html#csrf-integration-javascript-spa
64
  @Bean
65
  public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
66
    http
67
        .exceptionHandling(handling -> handling.authenticationEntryPoint(new Http403ForbiddenEntryPoint()))
68
        .oauth2Login(
69
            oauth2 -> oauth2.userInfoEndpoint(userInfo -> userInfo.userAuthoritiesMapper(this.userAuthoritiesMapper())))
70
        .csrf(csrf -> csrf
71
            .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
72
            .csrfTokenRequestHandler(new SpaCsrfTokenRequestHandler()))
73
        .addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class)
74
        .authorizeHttpRequests(auth -> auth.anyRequest().permitAll())
75
        .logout(logout -> logout.logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/"));
76
    return http.build();
77
  }
78
79
  @Bean
80
  public WebSecurityCustomizer webSecurityCustomizer() {
81
    return web -> web.ignoring().requestMatchers("/h2-console/**");
82
  }
83
84
  private GrantedAuthoritiesMapper userAuthoritiesMapper() {
85
    return (authorities) -> {
86
      Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
87
88
      authorities.forEach(authority -> {
89
        log.info("********** authority={}", authority);
90
        mappedAuthorities.add(authority);
91
        if (authority instanceof OAuth2UserAuthority oauth2UserAuthority) {
92
93
          Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes();
94
          log.info("********** userAttributes={}", userAttributes);
95
96
          mappedAuthorities.add(new SimpleGrantedAuthority("ROLE_USER"));
97
98
          String email = (String) userAttributes.get("email");
99
          if (getAdmin(email)) {
100
            mappedAuthorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
101
          }
102
103
104
          if (getDriver(email)) {
105
            mappedAuthorities.add(new SimpleGrantedAuthority("ROLE_DRIVER"));
106
          }
107
108
          if (getRider(email)) {
109
            mappedAuthorities.add(new SimpleGrantedAuthority("ROLE_RIDER"));
110
          }
111
112
          if (email.endsWith("@ucsb.edu")) {
113
            mappedAuthorities.add(new SimpleGrantedAuthority("ROLE_MEMBER"));
114
          }
115
        }
116
117
      });
118
      return mappedAuthorities;
119
    };
120
  }
121
122
  public boolean getAdmin(String email) {
123
    if (adminEmails.contains(email)) {
124
      return true;
125
    }
126
    Optional<User> u = userRepository.findByEmail(email);
127
    return u.isPresent() && u.get().getAdmin();
128
  }
129
130
131
  public boolean getDriver(String email){
132
    Optional<User> u = userRepository.findByEmail(email);
133
    return u.isPresent() && u.get().getDriver();
134
  }
135
136
  public boolean getRider(String email){
137
    Optional<User> u = userRepository.findByEmail(email);
138
    return u.isPresent() && u.get().getRider();
139
  }
140
141
  final class SpaCsrfTokenRequestHandler extends CsrfTokenRequestAttributeHandler {
142
    private final CsrfTokenRequestHandler delegate = new XorCsrfTokenRequestAttributeHandler();
143
144
    @Override
145
    public void handle(HttpServletRequest request, HttpServletResponse response,
146
        Supplier<CsrfToken> deferredCsrfToken) {
147
      /*
148
       * Always use XorCsrfTokenRequestAttributeHandler to provide BREACH protection
149
       * of
150
       * the CsrfToken when it is rendered in the response body.
151
       */
152 1 1. handle : removed call to org/springframework/security/web/csrf/CsrfTokenRequestHandler::handle → KILLED
      this.delegate.handle(request, response, deferredCsrfToken);
153
    }
154
155
    @Override
156
    public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) {
157
      /*
158
       * If the request contains a request header, use
159
       * CsrfTokenRequestAttributeHandler
160
       * to resolve the CsrfToken. This applies when a single-page application
161
       * includes
162
       * the header value automatically, which was obtained via a cookie containing
163
       * the
164
       * raw CsrfToken.
165
       */
166 1 1. resolveCsrfTokenValue : negated conditional → KILLED
      if (StringUtils.hasText(request.getHeader(csrfToken.getHeaderName()))) {
167 1 1. resolveCsrfTokenValue : replaced return value with "" for edu/ucsb/cs156/gauchoride/config/SecurityConfig$SpaCsrfTokenRequestHandler::resolveCsrfTokenValue → KILLED
        return super.resolveCsrfTokenValue(request, csrfToken);
168
      }
169
      /*
170
       * In all other cases (e.g. if the request contains a request parameter), use
171
       * XorCsrfTokenRequestAttributeHandler to resolve the CsrfToken. This applies
172
       * when a server-side rendered form includes the _csrf request parameter as a
173
       * hidden input.
174
       */
175 1 1. resolveCsrfTokenValue : replaced return value with "" for edu/ucsb/cs156/gauchoride/config/SecurityConfig$SpaCsrfTokenRequestHandler::resolveCsrfTokenValue → KILLED
      return this.delegate.resolveCsrfTokenValue(request, csrfToken);
176
    }
177
  }
178
179
  final class CsrfCookieFilter extends OncePerRequestFilter {
180
181
    @Override
182
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
183
        throws ServletException, IOException {
184
      CsrfToken csrfToken = (CsrfToken) request.getAttribute("_csrf");
185
      // Render the token value to a cookie by causing the deferred token to be loaded
186
      csrfToken.getToken();
187 1 1. doFilterInternal : removed call to jakarta/servlet/FilterChain::doFilter → KILLED
      filterChain.doFilter(request, response);
188
    }
189
  }
190
}

Mutations

152

1.1
Location : handle
Killed by : edu.ucsb.cs156.gauchoride.controllers.ShiftControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.gauchoride.controllers.ShiftControllerTests]/[method:logged_in_admin_cannot_get_driver_shifts()]
removed call to org/springframework/security/web/csrf/CsrfTokenRequestHandler::handle → KILLED

166

1.1
Location : resolveCsrfTokenValue
Killed by : edu.ucsb.cs156.gauchoride.controllers.UsersControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.gauchoride.controllers.UsersControllerTests]/[method:admin_tries_to_delete_non_existant_user_and_gets_right_error_message()]
negated conditional → KILLED

167

1.1
Location : resolveCsrfTokenValue
Killed by : edu.ucsb.cs156.gauchoride.config.SecurityConfigTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.gauchoride.config.SecurityConfigTests]/[method:test_SpaCsrfTokenRequestHandler()]
replaced return value with "" for edu/ucsb/cs156/gauchoride/config/SecurityConfig$SpaCsrfTokenRequestHandler::resolveCsrfTokenValue → KILLED

175

1.1
Location : resolveCsrfTokenValue
Killed by : edu.ucsb.cs156.gauchoride.controllers.UsersControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.gauchoride.controllers.UsersControllerTests]/[method:admin_tries_to_delete_non_existant_user_and_gets_right_error_message()]
replaced return value with "" for edu/ucsb/cs156/gauchoride/config/SecurityConfig$SpaCsrfTokenRequestHandler::resolveCsrfTokenValue → KILLED

187

1.1
Location : doFilterInternal
Killed by : edu.ucsb.cs156.gauchoride.controllers.ShiftControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.gauchoride.controllers.ShiftControllerTests]/[method:logged_in_admin_cannot_get_driver_shifts()]
removed call to jakarta/servlet/FilterChain::doFilter → KILLED

Active mutators

Tests examined


Report generated by PIT 1.7.3