SecurityConfig.java

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

Mutations

169

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

Active mutators

Tests examined


Report generated by PIT 1.17.0