AuthInterceptor.java

1
package org.cardanofoundation.explorer.api.interceptor;
2
3
import java.lang.reflect.Method;
4
import java.util.HashMap;
5
import java.util.List;
6
import java.util.Map;
7
import java.util.Objects;
8
import java.util.Set;
9
import java.util.function.Function;
10
import java.util.stream.Collectors;
11
12
import jakarta.annotation.PostConstruct;
13
import jakarta.servlet.http.HttpServletRequest;
14
import jakarta.servlet.http.HttpServletResponse;
15
16
import lombok.extern.log4j.Log4j2;
17
18
import org.springframework.data.redis.core.RedisTemplate;
19
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
20
import org.springframework.stereotype.Component;
21
import org.springframework.web.method.HandlerMethod;
22
import org.springframework.web.servlet.HandlerInterceptor;
23
24
import org.cardanofoundation.explorer.api.config.RsaConfig;
25
import org.cardanofoundation.explorer.api.exception.UnauthorizedException;
26
import org.cardanofoundation.explorer.api.interceptor.auth.Request;
27
import org.cardanofoundation.explorer.api.interceptor.auth.RoleConfigurationMapper;
28
import org.cardanofoundation.explorer.api.interceptor.auth.RoleFilterMapper;
29
import org.cardanofoundation.explorer.api.interceptor.auth.UserPrincipal;
30
import org.cardanofoundation.explorer.api.service.AuthService;
31
import org.cardanofoundation.explorer.api.util.JwtUtils;
32
import org.cardanofoundation.explorer.common.entity.enumeration.TokenAuthType;
33
import org.cardanofoundation.explorer.common.exception.BusinessException;
34
import org.cardanofoundation.explorer.common.exception.CommonErrorCode;
35
import org.cardanofoundation.explorer.common.exception.InvalidAccessTokenException;
36
import org.cardanofoundation.explorer.common.utils.StringUtils;
37
38
@Component
39
@Log4j2
40
public class AuthInterceptor implements HandlerInterceptor {
41
42
  private final RoleFilterMapper roleConf;
43
44
  private final RsaConfig rsaConfig;
45
46
  private final RedisTemplate<String, Object> redisTemplate;
47
48
  private final AuthService authService;
49
50
  private List<AntPathRequestMatcher> matchers;
51
52
  private Map<String, Request> authorEndpoint;
53
54
  public AuthInterceptor(
55
      RoleFilterMapper roleFilterMapper,
56
      RsaConfig rsaConfig,
57
      RedisTemplate<String, Object> redisTemplate,
58
      AuthService authService) {
59
    this.roleConf = roleFilterMapper;
60
    this.rsaConfig = rsaConfig;
61
    this.redisTemplate = redisTemplate;
62
    this.authService = authService;
63
  }
64
65
  @PostConstruct
66
  public void initAuth() {
67
    matchers =
68
        roleConf.getAuth().stream()
69
            .map(
70
                request -> {
71 1 1. lambda$initAuth$0 : negated conditional → NO_COVERAGE
                  if (org.springframework.util.StringUtils.hasText(request.getMethod())
72 1 1. lambda$initAuth$0 : negated conditional → NO_COVERAGE
                      || request.getMethod().equals("*")) {
73 1 1. lambda$initAuth$0 : replaced return value with null for org/cardanofoundation/explorer/api/interceptor/AuthInterceptor::lambda$initAuth$0 → NO_COVERAGE
                    return new AntPathRequestMatcher(request.getUri());
74
                  }
75 1 1. lambda$initAuth$0 : replaced return value with null for org/cardanofoundation/explorer/api/interceptor/AuthInterceptor::lambda$initAuth$0 → NO_COVERAGE
                  return new AntPathRequestMatcher(request.getUri(), request.getMethod());
76
                })
77
            .toList();
78
    authorEndpoint =
79
        roleConf.getAuth().stream().collect(Collectors.toMap(Request::getUri, Function.identity()));
80
  }
81
82
  @Override
83
  public boolean preHandle(
84
      HttpServletRequest request, HttpServletResponse httpServletResponse, Object handler)
85
      throws Exception {
86
    log.info("Authentication Interceptor is running...");
87
    HandlerMethod handlerMethod;
88
    try {
89
      handlerMethod = (HandlerMethod) handler;
90
    } catch (ClassCastException e) {
91
      log.error("Error handler: " + e.getMessage());
92 2 1. preHandle : replaced boolean return with true for org/cardanofoundation/explorer/api/interceptor/AuthInterceptor::preHandle → NO_COVERAGE
2. preHandle : replaced boolean return with false for org/cardanofoundation/explorer/api/interceptor/AuthInterceptor::preHandle → NO_COVERAGE
      return HandlerInterceptor.super.preHandle(request, httpServletResponse, handler);
93
    }
94
    Method method = handlerMethod.getMethod();
95
    log.info("Authentication Interceptor: {}", method);
96 1 1. preHandle : negated conditional → NO_COVERAGE
    if (method.isAnnotationPresent(IgnoreAuthentication.class)) {
97
      log.info("Ignore method if marked IgnoreAuthentication annotation");
98 1 1. preHandle : replaced boolean return with false for org/cardanofoundation/explorer/api/interceptor/AuthInterceptor::preHandle → NO_COVERAGE
      return true;
99
    }
100 1 1. preHandle : negated conditional → NO_COVERAGE
    if (method.getDeclaringClass().isAnnotationPresent(IgnoreAuthentication.class)) {
101
      log.info("Ignore class if marked IgnoreAuthentication annotation");
102 1 1. preHandle : replaced boolean return with false for org/cardanofoundation/explorer/api/interceptor/AuthInterceptor::preHandle → NO_COVERAGE
      return true;
103
    }
104
105 3 1. lambda$preHandle$1 : replaced boolean return with true for org/cardanofoundation/explorer/api/interceptor/AuthInterceptor::lambda$preHandle$1 → NO_COVERAGE
2. lambda$preHandle$1 : replaced boolean return with false for org/cardanofoundation/explorer/api/interceptor/AuthInterceptor::lambda$preHandle$1 → NO_COVERAGE
3. preHandle : negated conditional → NO_COVERAGE
    if (matchers.stream().noneMatch(matcher -> matcher.matches(request))) {
106 1 1. preHandle : replaced boolean return with false for org/cardanofoundation/explorer/api/interceptor/AuthInterceptor::preHandle → NO_COVERAGE
      return true;
107
    }
108
    UserPrincipal userPrincipal = new UserPrincipal();
109
    String token = getToken(request, userPrincipal);
110
    Set<String> roles = getRoles(token);
111
112 1 1. preHandle : removed call to org/cardanofoundation/explorer/api/interceptor/AuthInterceptor::checkRequestAllow → NO_COVERAGE
    checkRequestAllow(request, roles);
113
114
    Map<String, Map<String, Object>> roleDescription = new HashMap<>();
115
    for (RoleConfigurationMapper roleMapper : roleConf.getRoles()) {
116 1 1. preHandle : negated conditional → NO_COVERAGE
      if (roles.contains(roleMapper.getName())) {
117
        String roleKey = roleMapper.getName();
118
119 1 1. preHandle : negated conditional → NO_COVERAGE
        if (Objects.nonNull(roleMapper.getAttributes())) {
120
          roleDescription.put(roleKey, new HashMap<>(roleMapper.getAttributes()));
121
        }
122
      }
123
    }
124 1 1. preHandle : removed call to org/cardanofoundation/explorer/api/interceptor/auth/UserPrincipal::setRoleDescription → NO_COVERAGE
    userPrincipal.setRoleDescription(roleDescription);
125 1 1. preHandle : removed call to jakarta/servlet/http/HttpServletRequest::setAttribute → NO_COVERAGE
    request.setAttribute("user", userPrincipal);
126 1 1. preHandle : replaced boolean return with false for org/cardanofoundation/explorer/api/interceptor/AuthInterceptor::preHandle → NO_COVERAGE
    return true;
127
  }
128
129
  private void checkRequestAllow(HttpServletRequest request, Set<String> roles) {
130
    boolean isAllowed = false;
131 1 1. checkRequestAllow : negated conditional → NO_COVERAGE
    if (authorEndpoint.containsKey(request.getRequestURI())) {
132
      Request requestAuthor = authorEndpoint.get(request.getRequestURI());
133 1 1. checkRequestAllow : negated conditional → NO_COVERAGE
      if (requestAuthor.getRoles().length == 0) { // This feature doesn't require authorization
134
        isAllowed = true;
135
      }
136
      for (String role : requestAuthor.getRoles()) {
137 1 1. checkRequestAllow : negated conditional → NO_COVERAGE
        if (roles.contains(role)) {
138
          isAllowed = true;
139
        }
140
      }
141
    } else { // don't need to auth
142
      isAllowed = true;
143
    }
144 1 1. checkRequestAllow : negated conditional → NO_COVERAGE
    if (!isAllowed) {
145
      throw new UnauthorizedException();
146
    }
147
  }
148
149
  private String getToken(HttpServletRequest request, UserPrincipal userPrincipal) {
150
    String token = JwtUtils.parseJwt(request);
151
    try {
152 1 1. getToken : removed call to org/cardanofoundation/explorer/api/util/JwtUtils::validateJwtToken → NO_COVERAGE
      JwtUtils.validateJwtToken(token, rsaConfig.getPublicKey());
153
    } catch (Exception e) {
154
      throw new InvalidAccessTokenException();
155
    }
156 1 1. getToken : negated conditional → NO_COVERAGE
    if (isTokenBlacklisted(token)) {
157
      throw new InvalidAccessTokenException();
158
    }
159
    String username = JwtUtils.getAccountIdFromJwtToken(token, rsaConfig.getPublicKey());
160 1 1. getToken : removed call to org/cardanofoundation/explorer/api/interceptor/auth/UserPrincipal::setUsername → NO_COVERAGE
    userPrincipal.setUsername(username);
161 1 1. getToken : replaced return value with "" for org/cardanofoundation/explorer/api/interceptor/AuthInterceptor::getToken → NO_COVERAGE
    return token;
162
  }
163
164
  private Set<String> getRoles(String token) {
165 1 1. getRoles : replaced return value with Collections.emptySet for org/cardanofoundation/explorer/api/interceptor/AuthInterceptor::getRoles → NO_COVERAGE
    return JwtUtils.getRolesFromToken(token, rsaConfig.getPublicKey());
166
  }
167
168
  private boolean isTokenBlacklisted(String token) {
169 1 1. isTokenBlacklisted : negated conditional → NO_COVERAGE
    if (Boolean.TRUE.equals(StringUtils.isNullOrEmpty(token))) {
170
      throw new BusinessException(CommonErrorCode.INVALID_TOKEN);
171
    }
172 2 1. isTokenBlacklisted : replaced boolean return with true for org/cardanofoundation/explorer/api/interceptor/AuthInterceptor::isTokenBlacklisted → NO_COVERAGE
2. isTokenBlacklisted : replaced boolean return with false for org/cardanofoundation/explorer/api/interceptor/AuthInterceptor::isTokenBlacklisted → NO_COVERAGE
    return authService.isBlacklistToken(token, TokenAuthType.ACCESS_TOKEN);
173
  }
174
}

Mutations

71

1.1
Location : lambda$initAuth$0
Killed by : none
negated conditional → NO_COVERAGE

72

1.1
Location : lambda$initAuth$0
Killed by : none
negated conditional → NO_COVERAGE

73

1.1
Location : lambda$initAuth$0
Killed by : none
replaced return value with null for org/cardanofoundation/explorer/api/interceptor/AuthInterceptor::lambda$initAuth$0 → NO_COVERAGE

75

1.1
Location : lambda$initAuth$0
Killed by : none
replaced return value with null for org/cardanofoundation/explorer/api/interceptor/AuthInterceptor::lambda$initAuth$0 → NO_COVERAGE

92

1.1
Location : preHandle
Killed by : none
replaced boolean return with true for org/cardanofoundation/explorer/api/interceptor/AuthInterceptor::preHandle → NO_COVERAGE

2.2
Location : preHandle
Killed by : none
replaced boolean return with false for org/cardanofoundation/explorer/api/interceptor/AuthInterceptor::preHandle → NO_COVERAGE

96

1.1
Location : preHandle
Killed by : none
negated conditional → NO_COVERAGE

98

1.1
Location : preHandle
Killed by : none
replaced boolean return with false for org/cardanofoundation/explorer/api/interceptor/AuthInterceptor::preHandle → NO_COVERAGE

100

1.1
Location : preHandle
Killed by : none
negated conditional → NO_COVERAGE

102

1.1
Location : preHandle
Killed by : none
replaced boolean return with false for org/cardanofoundation/explorer/api/interceptor/AuthInterceptor::preHandle → NO_COVERAGE

105

1.1
Location : lambda$preHandle$1
Killed by : none
replaced boolean return with true for org/cardanofoundation/explorer/api/interceptor/AuthInterceptor::lambda$preHandle$1 → NO_COVERAGE

2.2
Location : lambda$preHandle$1
Killed by : none
replaced boolean return with false for org/cardanofoundation/explorer/api/interceptor/AuthInterceptor::lambda$preHandle$1 → NO_COVERAGE

3.3
Location : preHandle
Killed by : none
negated conditional → NO_COVERAGE

106

1.1
Location : preHandle
Killed by : none
replaced boolean return with false for org/cardanofoundation/explorer/api/interceptor/AuthInterceptor::preHandle → NO_COVERAGE

112

1.1
Location : preHandle
Killed by : none
removed call to org/cardanofoundation/explorer/api/interceptor/AuthInterceptor::checkRequestAllow → NO_COVERAGE

116

1.1
Location : preHandle
Killed by : none
negated conditional → NO_COVERAGE

119

1.1
Location : preHandle
Killed by : none
negated conditional → NO_COVERAGE

124

1.1
Location : preHandle
Killed by : none
removed call to org/cardanofoundation/explorer/api/interceptor/auth/UserPrincipal::setRoleDescription → NO_COVERAGE

125

1.1
Location : preHandle
Killed by : none
removed call to jakarta/servlet/http/HttpServletRequest::setAttribute → NO_COVERAGE

126

1.1
Location : preHandle
Killed by : none
replaced boolean return with false for org/cardanofoundation/explorer/api/interceptor/AuthInterceptor::preHandle → NO_COVERAGE

131

1.1
Location : checkRequestAllow
Killed by : none
negated conditional → NO_COVERAGE

133

1.1
Location : checkRequestAllow
Killed by : none
negated conditional → NO_COVERAGE

137

1.1
Location : checkRequestAllow
Killed by : none
negated conditional → NO_COVERAGE

144

1.1
Location : checkRequestAllow
Killed by : none
negated conditional → NO_COVERAGE

152

1.1
Location : getToken
Killed by : none
removed call to org/cardanofoundation/explorer/api/util/JwtUtils::validateJwtToken → NO_COVERAGE

156

1.1
Location : getToken
Killed by : none
negated conditional → NO_COVERAGE

160

1.1
Location : getToken
Killed by : none
removed call to org/cardanofoundation/explorer/api/interceptor/auth/UserPrincipal::setUsername → NO_COVERAGE

161

1.1
Location : getToken
Killed by : none
replaced return value with "" for org/cardanofoundation/explorer/api/interceptor/AuthInterceptor::getToken → NO_COVERAGE

165

1.1
Location : getRoles
Killed by : none
replaced return value with Collections.emptySet for org/cardanofoundation/explorer/api/interceptor/AuthInterceptor::getRoles → NO_COVERAGE

169

1.1
Location : isTokenBlacklisted
Killed by : none
negated conditional → NO_COVERAGE

172

1.1
Location : isTokenBlacklisted
Killed by : none
replaced boolean return with true for org/cardanofoundation/explorer/api/interceptor/AuthInterceptor::isTokenBlacklisted → NO_COVERAGE

2.2
Location : isTokenBlacklisted
Killed by : none
replaced boolean return with false for org/cardanofoundation/explorer/api/interceptor/AuthInterceptor::isTokenBlacklisted → NO_COVERAGE

Active mutators

Tests examined


Report generated by PIT 1.14.2