• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

oracle / opengrok / #3671

01 Nov 2023 10:10AM UTC coverage: 66.019% (-9.1%) from 75.16%
#3671

push

web-flow
Fix Sonar codesmell issues (#4460)

Signed-off-by: Gino Augustine <ginoaugustine@gmail.com>

308 of 308 new or added lines in 27 files covered. (100.0%)

38690 of 58604 relevant lines covered (66.02%)

0.66 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.11
/opengrok-web/src/main/java/org/opengrok/web/api/v1/filter/IncomingFilter.java
1
/*
2
 * CDDL HEADER START
3
 *
4
 * The contents of this file are subject to the terms of the
5
 * Common Development and Distribution License (the "License").
6
 * You may not use this file except in compliance with the License.
7
 *
8
 * See LICENSE.txt included in this distribution for the specific
9
 * language governing permissions and limitations under the License.
10
 *
11
 * When distributing Covered Code, include this CDDL HEADER in each
12
 * file and include the License file at LICENSE.txt.
13
 * If applicable, add the following below this CDDL HEADER, with the
14
 * fields enclosed by brackets "[]" replaced with your own identifying
15
 * information: Portions Copyright [yyyy] [name of copyright owner]
16
 *
17
 * CDDL HEADER END
18
 */
19

20
/*
21
 * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved.
22
 */
23
package org.opengrok.web.api.v1.filter;
24

25
import jakarta.annotation.PostConstruct;
26
import jakarta.servlet.http.HttpServletRequest;
27
import jakarta.ws.rs.container.ContainerRequestContext;
28
import jakarta.ws.rs.container.ContainerRequestFilter;
29
import jakarta.ws.rs.container.PreMatching;
30
import jakarta.ws.rs.core.Context;
31
import jakarta.ws.rs.core.HttpHeaders;
32
import jakarta.ws.rs.core.Response;
33
import jakarta.ws.rs.ext.Provider;
34
import org.opengrok.indexer.configuration.ConfigurationChangedListener;
35
import org.opengrok.indexer.configuration.RuntimeEnvironment;
36
import org.opengrok.indexer.logger.LoggerFactory;
37
import org.opengrok.web.api.v1.controller.AnnotationController;
38
import org.opengrok.web.api.v1.controller.FileController;
39
import org.opengrok.web.api.v1.controller.HistoryController;
40
import org.opengrok.web.api.v1.controller.SearchController;
41
import org.opengrok.web.api.v1.controller.SuggesterController;
42
import org.opengrok.web.api.v1.controller.SystemController;
43

44
import java.io.IOException;
45
import java.net.InetAddress;
46
import java.util.Arrays;
47
import java.util.HashSet;
48
import java.util.Optional;
49
import java.util.Set;
50
import java.util.logging.Level;
51
import java.util.logging.Logger;
52

53
/**
54
 * This filter allows the request in case it contains the correct authentication bearer token
55
 * (needs to come in via HTTPS) or it is coming from localhost or its path matches the list
56
 * of built in paths.
57
 * If the request does not contain valid token and appears to come from localhost however is proxied
58
 * (contains either X-Forwarded-For or Forwarded HTTP headers) it is denied.
59
 */
60
@Provider
61
@PreMatching
62
public class IncomingFilter implements ContainerRequestFilter, ConfigurationChangedListener {
1✔
63

64
    private static final Logger LOGGER = LoggerFactory.getLogger(IncomingFilter.class);
1✔
65

66
    /**
67
     * Endpoint paths that are exempted from this filter.
68
     * @see SearchController#search(HttpServletRequest, String, String, String, String, String, String,
69
     * java.util.List, int, int)
70
     * @see SuggesterController#getSuggestions(org.opengrok.web.api.v1.suggester.model.SuggesterQueryData)
71
     * @see SuggesterController#getConfig()
72
     */
73
    private static final Set<String> allowedPaths = new HashSet<>(Arrays.asList(
1✔
74
            SearchController.PATH, SuggesterController.PATH, SuggesterController.PATH + "/config",
75
            HistoryController.PATH, FileController.PATH, AnnotationController.PATH,
76
            SystemController.PATH + "/ping"));
77

78
    @Context
79
    private HttpServletRequest request;
80

81
    private final Set<String> localAddresses = new HashSet<>(Arrays.asList(
1✔
82
            "127.0.0.1", "0:0:0:0:0:0:0:1", "localhost"
83
    ));
84

85
    static final String BEARER = "Bearer ";  // Authorization header value prefix
86

87
    private Set<String> tokens;
88

89
    private Set<String> getTokens() {
90
        return tokens;
1✔
91
    }
92

93
    private void setTokens(Set<String> tokens) {
94
        this.tokens = tokens;
1✔
95
    }
1✔
96

97
    @PostConstruct
98
    public void init() {
99
        try {
100
            localAddresses.add(InetAddress.getLocalHost().getHostAddress());
1✔
101
            for (InetAddress inetAddress : InetAddress.getAllByName("localhost")) {
1✔
102
                localAddresses.add(inetAddress.getHostAddress());
1✔
103
            }
104
        } catch (IOException e) {
×
105
            LOGGER.log(Level.SEVERE, "Could not get localhost addresses", e);
×
106
        }
1✔
107

108
        // Cache the tokens to avoid locking.
109
        setTokens(RuntimeEnvironment.getInstance().getAuthenticationTokens());
1✔
110

111
        RuntimeEnvironment.getInstance().registerListener(this);
1✔
112
    }
1✔
113

114
    @Override
115
    public void onConfigurationChanged() {
116
        LOGGER.log(Level.FINER, "refreshing token cache");
1✔
117
        setTokens(RuntimeEnvironment.getInstance().getAuthenticationTokens());
1✔
118
    }
1✔
119

120
    @Override
121
    public void filter(final ContainerRequestContext context) {
122
        if (request == null) { // happens in tests
1✔
123
            return;
×
124
        }
125

126
        String path = context.getUriInfo().getPath();
1✔
127
        boolean isTokenValid = Optional.ofNullable(request.getHeader(HttpHeaders.AUTHORIZATION))
1✔
128
                .filter(authHeaderValue -> authHeaderValue.startsWith(BEARER))
1✔
129
                .map(authHeaderValue -> authHeaderValue.substring(BEARER.length()))
1✔
130
                .filter(getTokens()::contains)
1✔
131
                .isPresent();
1✔
132
        if (isTokenValid) {
1✔
133
            var sanitizedPath = path.replaceAll("[\n\r]", "_");
1✔
134
            if (request.isSecure() || RuntimeEnvironment.getInstance().isAllowInsecureTokens()) {
1✔
135
                LOGGER.log(Level.FINEST, "allowing request to {0} based on authentication token", sanitizedPath);
1✔
136
                return;
1✔
137
            } else {
138
                LOGGER.log(Level.FINEST, "request to {0} has a valid token however is not secure", sanitizedPath);
×
139
            }
140
        }
141

142
        if (allowedPaths.contains(path)) {
1✔
143
            LOGGER.log(Level.FINEST, "allowing request to {0} based on allow listed path", path);
1✔
144
            return;
1✔
145
        }
146

147
        // In a reverse proxy environment the connection appears to be coming from localhost.
148
        // These request should really be using tokens.
149
        if (request.getHeader("X-Forwarded-For") != null || request.getHeader("Forwarded") != null) {
1✔
150
            LOGGER.log(Level.FINEST, "denying request to {0} due to existence of forwarded header in the request",
1✔
151
                    path);
152
            context.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
1✔
153
            return;
1✔
154
        }
155

156
        if (localAddresses.contains(request.getRemoteAddr())) {
1✔
157
            LOGGER.log(Level.FINEST, "allowing request to {0} based on localhost IP address", path);
1✔
158
            return;
1✔
159
        }
160

161
        context.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
1✔
162
    }
1✔
163
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc