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 |
|
72 |
1.1 |
|
73 |
1.1 |
|
75 |
1.1 |
|
92 |
1.1 2.2 |
|
96 |
1.1 |
|
98 |
1.1 |
|
100 |
1.1 |
|
102 |
1.1 |
|
105 |
1.1 2.2 3.3 |
|
106 |
1.1 |
|
112 |
1.1 |
|
116 |
1.1 |
|
119 |
1.1 |
|
124 |
1.1 |
|
125 |
1.1 |
|
126 |
1.1 |
|
131 |
1.1 |
|
133 |
1.1 |
|
137 |
1.1 |
|
144 |
1.1 |
|
152 |
1.1 |
|
156 |
1.1 |
|
160 |
1.1 |
|
161 |
1.1 |
|
165 |
1.1 |
|
169 |
1.1 |
|
172 |
1.1 2.2 |