Copilot commented on code in PR #896:
URL: https://github.com/apache/ranger/pull/896#discussion_r3006586074


##########
pdp/src/main/java/org/apache/ranger/pdp/RangerPdpStatusServlet.java:
##########
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.ranger.pdp;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.ranger.pdp.config.RangerPdpConfig;
+import org.apache.ranger.pdp.config.RangerPdpConstants;
+
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.core.MediaType;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class RangerPdpStatusServlet extends HttpServlet {
+    private static final ObjectMapper MAPPER = new ObjectMapper();
+
+    private final RangerPdpStats  runtimeState;
+    private final RangerPdpConfig config;
+    private final Mode            mode;
+
+    public enum Mode { LIVE, READY, METRICS }
+
+    public RangerPdpStatusServlet(RangerPdpStats runtimeState, RangerPdpConfig 
config, Mode mode) {
+        this.runtimeState = runtimeState;
+        this.config       = config;
+        this.mode         = mode;
+    }
+
+    @Override
+    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
throws IOException {
+        switch (mode) {
+            case LIVE:
+                writeLive(resp);
+                break;
+            case READY:
+                writeReady(resp);
+                break;
+            case METRICS:
+                writeMetrics(resp);
+                break;
+            default:
+                resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
+        }
+    }
+
+    private void writeLive(HttpServletResponse resp) throws IOException {
+        Map<String, Object> payload = new LinkedHashMap<>();
+
+        payload.put("status", runtimeState.isServerStarted() ? "UP" : "DOWN");
+        payload.put("service", "ranger-pdp");
+        payload.put("live", runtimeState.isServerStarted());
+
+        resp.setStatus(runtimeState.isServerStarted() ? 
HttpServletResponse.SC_OK : HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+        resp.setContentType(MediaType.APPLICATION_JSON);
+
+        MAPPER.writeValue(resp.getOutputStream(), payload);
+    }
+
+    private void writeReady(HttpServletResponse resp) throws IOException {
+        boolean ready = runtimeState.isServerStarted() && 
runtimeState.isAuthorizerInitialized() && runtimeState.isAcceptingRequests();
+
+        Map<String, Object> payload = new LinkedHashMap<>();
+
+        payload.put("status", ready ? "READY" : "NOT_READY");
+        payload.put("service", "ranger-pdp");
+        payload.put("ready", ready);
+        payload.put("authorizerInitialized", 
runtimeState.isAuthorizerInitialized());
+        payload.put("acceptingRequests", runtimeState.isAcceptingRequests());
+        payload.put("policyCacheAgeMs", getPolicyCacheAgeMs());
+
+        resp.setStatus(ready ? HttpServletResponse.SC_OK : 
HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+        resp.setContentType(MediaType.APPLICATION_JSON);
+
+        MAPPER.writeValue(resp.getOutputStream(), payload);
+    }
+
+    private void writeMetrics(HttpServletResponse resp) throws IOException {
+        StringBuilder sb = new StringBuilder(512);
+
+        sb.append("# TYPE ranger_pdp_requests_total counter\n");
+        sb.append("ranger_pdp_requests_total 
").append(runtimeState.getTotalRequests()).append('\n');
+        sb.append("# TYPE ranger_pdp_requests_success_total counter\n");
+        sb.append("ranger_pdp_requests_success_total 
").append(runtimeState.getTotalAuthzSuccess()).append('\n');
+        sb.append("# TYPE ranger_pdp_requests_bad_request_total counter\n");
+        sb.append("ranger_pdp_requests_bad_request_total 
").append(runtimeState.getTotalAuthzBadRequest()).append('\n');
+        sb.append("# TYPE ranger_pdp_requests_error_total counter\n");
+        sb.append("ranger_pdp_requests_error_total 
").append(runtimeState.getTotalAuthzErrors()).append('\n');
+        sb.append("# TYPE ranger_pdp_auth_failures_total counter\n");
+        sb.append("ranger_pdp_auth_failures_total 
").append(runtimeState.getTotalAuthFailures()).append('\n');
+        sb.append("# TYPE ranger_pdp_request_latency_avg_ms gauge\n");
+        sb.append("ranger_pdp_request_latency_avg_ms 
").append(runtimeState.getAverageLatencyMs()).append('\n');
+        sb.append("# TYPE ranger_pdp_policy_cache_age_ms gauge\n");
+        sb.append("ranger_pdp_policy_cache_age_ms 
").append(getPolicyCacheAgeMs()).append('\n');
+
+        resp.setStatus(HttpServletResponse.SC_OK);
+        resp.setContentType("text/plain; version=0.0.4");
+
+        resp.getWriter().write(sb.toString());
+    }
+
+    private long getPolicyCacheAgeMs() {
+        String cacheDir = 
config.get(RangerPdpConstants.PROP_AUTHZ_POLICY_CACHE_DIR, null);
+
+        if (StringUtils.isNotBlank(cacheDir)) {
+            File dir = new File(cacheDir);
+
+            if (dir.exists() && dir.isDirectory()) {
+                File[] files = dir.listFiles((d, name) -> 
name.endsWith(".json"));
+
+                if (files != null && files.length > 0) {
+                    long latestMtime = 0L;
+
+                    for (File f : files) {
+                        latestMtime = Math.max(latestMtime, f.lastModified());
+                    }
+
+                    if (latestMtime > 0L) {
+                        return Math.max(0L, System.currentTimeMillis() - 
latestMtime);
+                    }
+                }
+            }
+        }
+
+        return -1;

Review Comment:
   `getPolicyCacheAgeMs()` scans the policy-cache directory and stats every 
`*.json` file on each `/health/ready` and `/metrics` request. With frequent 
Prometheus scraping (and/or large cache dirs) this can become unnecessary 
filesystem overhead. Consider caching the computed value for a short TTL, 
tracking latest mtime incrementally, or moving this expensive check behind a 
lower-frequency refresh.



##########
pdp/src/main/java/org/apache/ranger/pdp/config/RangerPdpConfig.java:
##########
@@ -0,0 +1,281 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.ranger.pdp.config;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.ranger.plugin.util.XMLUtils;
+import org.ietf.jgss.GSSCredential;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.util.Properties;
+
+/**
+ * Reads Ranger PDP configuration from {@code ranger-pdp-default.xml} 
(classpath)
+ * overridden by {@code ranger-pdp-site.xml} (classpath or filesystem).
+ *
+ * <p>Both files use the Hadoop {@code <configuration>} XML format, consistent
+ * with other Ranger server modules (tagsync, kms, etc.).
+ * The format is parsed directly using the JDK DOM API to avoid an early
+ * class-load dependency on Hadoop's {@code Configuration} class.
+ *
+ * <p>Authentication property names:
+ * <ul>
+ *   <li>Kerberos/SPNEGO:    {@code ranger.pdp.authn.kerberos.*}
+ *   <li>JWT bearer token:   {@code ranger.pdp.authn.jwt.*}
+ *   <li>HTTP header:        {@code ranger.pdp.authn.header.*}
+ * </ul>
+ */
+public class RangerPdpConfig {
+    private static final Logger LOG = 
LoggerFactory.getLogger(RangerPdpConfig.class);
+
+    private static final String DEFAULT_CONFIG_FILE = "ranger-pdp-default.xml";
+    private static final String SITE_CONFIG_FILE    = "ranger-pdp-site.xml";
+
+    private final Properties props = new Properties();
+
+    public RangerPdpConfig() {
+        loadFromClasspath(DEFAULT_CONFIG_FILE);
+        loadFromClasspath(SITE_CONFIG_FILE);
+
+        String confDir = System.getProperty(RangerPdpConstants.PROP_CONF_DIR, 
"");
+
+        if (StringUtils.isNotBlank(confDir)) {
+            loadFromFile(new File(confDir, SITE_CONFIG_FILE));
+        }
+
+        applySystemPropertyOverrides();
+
+        LOG.info("RangerPdpConfig initialized (conf.dir={})", confDir);
+    }
+
+    public int getPort() {
+        return getInt(RangerPdpConstants.PROP_PORT, 6500);
+    }
+
+    public String getLogDir() {
+        return get(RangerPdpConstants.PROP_LOG_DIR, "/var/log/ranger/pdp");
+    }
+
+    public boolean isSslEnabled() {
+        return getBoolean(RangerPdpConstants.PROP_SSL_ENABLED, false);
+    }
+
+    public String getKeystoreFile() {
+        return get(RangerPdpConstants.PROP_SSL_KEYSTORE_FILE, "");
+    }
+
+    public String getKeystorePassword() {
+        return get(RangerPdpConstants.PROP_SSL_KEYSTORE_PASSWORD, "");
+    }
+
+    public String getKeystoreType() {
+        return get(RangerPdpConstants.PROP_SSL_KEYSTORE_TYPE, "JKS");
+    }
+
+    public boolean isTruststoreEnabled() {
+        return getBoolean(RangerPdpConstants.PROP_SSL_TRUSTSTORE_ENABLED, 
false);
+    }
+
+    public String getTruststoreFile() {
+        return get(RangerPdpConstants.PROP_SSL_TRUSTSTORE_FILE, "");
+    }
+
+    public String getTruststorePassword() {
+        return get(RangerPdpConstants.PROP_SSL_TRUSTSTORE_PASSWORD, "");
+    }
+
+    public String getTruststoreType() {
+        return get(RangerPdpConstants.PROP_SSL_TRUSTSTORE_TYPE, "JKS");
+    }
+
+    public boolean isHttp2Enabled() {
+        return getBoolean(RangerPdpConstants.PROP_HTTP2_ENABLED, true);
+    }
+
+    public int getHttpConnectorMaxThreads() {
+        return getInt(RangerPdpConstants.PROP_HTTP_CONNECTOR_MAX_THREADS, 200);
+    }
+
+    public int getHttpConnectorMinSpareThreads() {
+        return 
getInt(RangerPdpConstants.PROP_HTTP_CONNECTOR_MIN_SPARE_THREADS, 20);
+    }
+
+    public int getHttpConnectorAcceptCount() {
+        return getInt(RangerPdpConstants.PROP_HTTP_CONNECTOR_ACCEPT_COUNT, 
100);
+    }
+
+    public int getHttpConnectorMaxConnections() {
+        return getInt(RangerPdpConstants.PROP_HTTP_CONNECTOR_MAX_CONNECTIONS, 
10000);
+    }
+
+    public String getAuthnTypes() {
+        return get(RangerPdpConstants.PROP_AUTHN_TYPES, "header,jwt,kerberos");
+    }
+
+    // --- HTTP Header auth ---
+    public boolean isHeaderAuthnEnabled() {
+        return getBoolean(RangerPdpConstants.PROP_AUTHN_HEADER_ENABLED, false);
+    }
+
+    public String getHeaderAuthnUsername() {
+        return get(RangerPdpConstants.PROP_AUTHN_HEADER_USERNAME, 
"X-Forwarded-User");
+    }
+
+    // --- JWT bearer token auth ---
+    public boolean isJwtAuthnEnabled() {
+        return getBoolean(RangerPdpConstants.PROP_AUTHN_JWT_ENABLED, false);
+    }
+
+    public String getJwtProviderUrl() {
+        return get(RangerPdpConstants.PROP_AUTHN_JWT_PROVIDER_URL, "");
+    }
+
+    public String getJwtPublicKey() {
+        return get(RangerPdpConstants.PROP_AUTHN_JWT_PUBLIC_KEY, "");
+    }
+
+    public String getJwtCookieName() {
+        return get(RangerPdpConstants.PROP_AUTHN_JWT_COOKIE_NAME, 
"hadoop-jwt");
+    }
+
+    public String getJwtAudiences() {
+        return get(RangerPdpConstants.PROP_AUTHN_JWT_AUDIENCES, "");
+    }
+
+    // --- Kerberos / SPNEGO ---
+    public boolean isKerberosAuthnEnabled() {
+        return getBoolean(RangerPdpConstants.PROP_AUTHN_KERBEROS_ENABLED, 
false);
+    }
+
+    public String getSpnegoPrincipal() {
+        return get(RangerPdpConstants.PROP_AUTHN_KERBEROS_SPNEGO_PRINCIPAL, 
"");
+    }
+
+    public String getSpnegoKeytab() {
+        return get(RangerPdpConstants.PROP_AUTHN_KERBEROS_SPNEGO_KEYTAB, "");
+    }
+
+    public int getKerberosTokenValiditySeconds() {
+        return 
getInt(RangerPdpConstants.PROP_AUTHN_KERBEROS_KRB_TOKEN_VALIDITY, 
GSSCredential.INDEFINITE_LIFETIME);
+    }
+
+    public String getKerberosNameRules() {
+        return get(RangerPdpConstants.PROP_AUTHN_KERBEROS_NAME_RULES, 
"DEFAULT");
+    }
+
+    /**
+     * Returns all properties for forwarding to {@code 
RangerEmbeddedAuthorizer}.
+     */
+    public Properties getAuthzProperties() {
+        return new Properties(props);
+    }
+
+    public String get(String key, String defaultValue) {
+        String val = props.getProperty(key);
+
+        return StringUtils.isNotBlank(val) ? val.trim() : defaultValue;
+    }
+
+    public int getInt(String key, int defaultValue) {
+        String val = props.getProperty(key);
+
+        if (StringUtils.isNotBlank(val)) {
+            try {
+                return Integer.parseInt(val.trim());
+            } catch (NumberFormatException e) {
+                LOG.warn("Invalid integer for {}: '{}'; using default {}", 
key, val, defaultValue);
+            }
+        }
+
+        return defaultValue;
+    }
+
+    public boolean getBoolean(String key, boolean defaultValue) {
+        String val = props.getProperty(key);
+
+        return StringUtils.isNotBlank(val) ? Boolean.parseBoolean(val.trim()) 
: defaultValue;
+    }
+
+    private void loadFromClasspath(String resourceName) {
+        try (InputStream in = 
getClass().getClassLoader().getResourceAsStream(resourceName)) {
+            if (in != null) {
+                parseHadoopXml(in, resourceName);
+            } else {
+                LOG.debug("Config resource not found on classpath: {}", 
resourceName);
+            }
+        } catch (IOException e) {
+            LOG.warn("Failed to close stream for classpath resource: {}", 
resourceName, e);
+        }
+    }
+
+    private void loadFromFile(File file) {
+        if (!file.exists() || !file.isFile()) {
+            LOG.debug("Config file not found: {}", file);
+            return;
+        }
+
+        try (InputStream in = Files.newInputStream(file.toPath())) {
+            parseHadoopXml(in, file.getAbsolutePath());
+        } catch (IOException e) {
+            LOG.warn("Failed to read config file: {}", file, e);
+        }
+    }
+
+    /**
+     * Parses a Hadoop-style {@code <configuration>} XML document and merges 
all
+     * {@code <property>} entries into {@link #props}.  Later entries override 
earlier
+     * ones, matching Hadoop's own override semantics.
+     *
+     * <pre>
+     * {@code
+     * <configuration>
+     *   <property>
+     *     <name>some.key</name>
+     *     <value>some-value</value>
+     *   </property>
+     * </configuration>
+     * }
+     * </pre>
+     */
+    private void parseHadoopXml(InputStream in, String source) {
+        LOG.info("Loading from {}. Properties count {}", source, props.size());
+
+        XMLUtils.loadConfig(in, props);
+
+        LOG.info("Loaded from {}. Properties count {}", source, props.size());
+    }

Review Comment:
   `parseHadoopXml()` logs config-load progress at INFO for every config 
source, which will add noisy startup/runtime logs (and is inconsistent with 
other Ranger config loaders that don’t log each load at INFO). Consider 
downgrading these messages to DEBUG (or logging only on failures) to keep 
production logs clean.



##########
pdp/src/main/java/org/apache/ranger/pdp/rest/RangerPdpREST.java:
##########
@@ -0,0 +1,486 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.ranger.pdp.rest;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.collections.MapUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.ranger.authz.api.RangerAuthorizer;
+import org.apache.ranger.authz.api.RangerAuthzException;
+import org.apache.ranger.authz.model.RangerAccessInfo;
+import org.apache.ranger.authz.model.RangerAuthzRequest;
+import org.apache.ranger.authz.model.RangerAuthzResult;
+import org.apache.ranger.authz.model.RangerMultiAuthzRequest;
+import org.apache.ranger.authz.model.RangerMultiAuthzResult;
+import org.apache.ranger.authz.model.RangerResourceInfo;
+import org.apache.ranger.authz.model.RangerResourcePermissions;
+import org.apache.ranger.authz.model.RangerResourcePermissionsRequest;
+import org.apache.ranger.authz.model.RangerUserInfo;
+import org.apache.ranger.pdp.RangerPdpStats;
+import org.apache.ranger.pdp.config.RangerPdpConfig;
+import org.apache.ranger.pdp.config.RangerPdpConstants;
+import org.apache.ranger.pdp.model.ErrorResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.PostConstruct;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
+import static javax.ws.rs.core.Response.Status.FORBIDDEN;
+import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR;
+import static javax.ws.rs.core.Response.Status.UNAUTHORIZED;
+import static 
org.apache.ranger.authz.api.RangerAuthzApiErrorCode.INVALID_REQUEST_USER_INFO_MISSING;
+import static 
org.apache.ranger.pdp.config.RangerPdpConstants.PROP_PDP_SERVICE_PREFIX;
+import static 
org.apache.ranger.pdp.config.RangerPdpConstants.PROP_SUFFIX_DELEGATION_USERS;
+import static 
org.apache.ranger.pdp.config.RangerPdpConstants.WILDCARD_SERVICE_NAME;
+
+/**
+ * REST resource that exposes the three core {@link RangerAuthorizer} methods 
over HTTP.
+ *
+ * <p>All endpoints are under {@code /authz/v1} and produce/consume {@code 
application/json}.
+ * Authentication is enforced upstream by {@link 
org.apache.ranger.pdp.security.RangerPdpAuthNFilter}; the authenticated
+ * caller's identity is read from the {@link 
RangerPdpConstants#ATTR_AUTHENTICATED_USER}
+ * request attribute.
+ *
+ * <table border="1">
+ *   <tr><th>Method</th><th>Path</th><th>Request body</th><th>Response 
body</th></tr>
+ *   <tr><td>POST</td><td>/authz/v1/authorize</td>
+ *       <td>{@link RangerAuthzRequest}</td><td>{@link 
RangerAuthzResult}</td></tr>
+ *   <tr><td>POST</td><td>/authz/v1/authorizeMulti</td>
+ *       <td>{@link RangerMultiAuthzRequest}</td><td>{@link 
RangerMultiAuthzResult}</td></tr>
+ *   <tr><td>POST</td><td>/authz/v1/permissions</td>
+ *       <td>{@link RangerResourcePermissionsRequest}</td>
+ *       <td>{@link RangerResourcePermissions}</td></tr>
+ * </table>
+ */
+@Path("/v1")
+@Produces(MediaType.APPLICATION_JSON)
+@Consumes(MediaType.APPLICATION_JSON)
+@Singleton
+public class RangerPdpREST {
+    private static final Logger LOG = 
LoggerFactory.getLogger(RangerPdpREST.class);
+
+    private static final Response RESPONSE_OK = Response.ok().build();
+
+    private final Map<String, Set<String>> delegationUsersByService = new 
HashMap<>();
+
+    @Inject
+    private RangerAuthorizer authorizer;
+
+    @Inject
+    private RangerPdpConfig config;
+
+    @Context
+    private ServletContext servletContext;
+
+    @PostConstruct
+    public void initialize() {
+        initializeDelegationUsers();
+    }
+
+    /**
+     * Evaluates a single access request.
+     *
+     * @param request the authorization request
+     * @return {@code 200 OK} with {@link RangerAuthzResult}, or {@code 400} / 
{@code 500} on error
+     */
+    @POST
+    @Path("/authorize")
+    public Response authorize(RangerAuthzRequest request, @Context 
HttpServletRequest httpRequest) {
+        long     startNanos = System.nanoTime();
+        Response ret        = null;
+
+        try {
+            String           requestId   = request != null ? 
request.getRequestId() : null;
+            String           caller      = getAuthenticatedUser(httpRequest);
+            String           serviceName = getServiceName(request);
+            RangerUserInfo   user        = request != null ? request.getUser() 
: null;
+            RangerAccessInfo access      = request != null ? 
request.getAccess() : null;
+
+            LOG.debug("==> authorize(requestId={}, caller={}, 
serviceName={})", requestId, caller, serviceName);
+
+            ret = validateCaller(caller, user, access, serviceName);
+
+            if (RESPONSE_OK.equals(ret)) {
+                try {

Review Comment:
   Using a concrete `Response` instance (`RESPONSE_OK`) as a sentinel and 
checking `RESPONSE_OK.equals(ret)` is brittle because `Response` equality is 
effectively object identity here. If `validateCaller()` is ever refactored to 
return a newly-built 200 response, this check will silently fail. Consider 
returning a boolean/enum from `validateCaller()` (or checking 
`ret.getStatus()`), and building the actual response in the caller.



##########
intg/src/main/python/apache_ranger/exceptions.py:
##########
@@ -33,17 +33,21 @@ def __init__(self, api, response):
         self.msgDesc         = None
         self.messageList     = None
 
-        print(response)
-
         if api is not None and response is not None:
             if response.content:
-              try:
-                respJson         = response.json()
-                self.msgDesc     = respJson['msgDesc']     if respJson is not 
None and 'msgDesc'     in respJson else None
-                self.messageList = respJson['messageList'] if respJson is not 
None and 'messageList' in respJson else None
-              except Exception:
-                self.msgDesc     = response.content
-                self.messageList = [ response.content ]
+                try:
+                    respJson = response.json()
+
+                    if respJson:
+                        self.msgDesc     = respJson['msgDesc']     if 
'msgDesc'     in respJson else None
+                        self.messageList = respJson['messageList'] if 
'messageList' in respJson else None
+
+                        if self.msgDesc is None:
+                            self.msgDesc = respJson['message'] if 'message' in 
respJson else None
+
+                except Exception:
+                    self.msgDesc     = response.content
+                    self.messageList = [ response.content ]

Review Comment:
   In the JSON parse failure path, `msgDesc`/`messageList` are populated from 
`response.content`, which is `bytes` in `requests`. This produces exception 
messages like `b'...'` and can leak raw bytes; it’s better to use 
`response.text` (or decode `response.content` with a safe charset/replace 
strategy).
   ```suggestion
                       self.msgDesc     = response.text
                       self.messageList = [ response.text ]
   ```



##########
pdp/src/main/java/org/apache/ranger/pdp/security/KerberosAuthNHandler.java:
##########
@@ -0,0 +1,275 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.ranger.pdp.security;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hadoop.security.authentication.util.KerberosName;
+import org.apache.ranger.pdp.config.RangerPdpConstants;
+import org.ietf.jgss.GSSContext;
+import org.ietf.jgss.GSSCredential;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.GSSManager;
+import org.ietf.jgss.GSSName;
+import org.ietf.jgss.Oid;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.login.Configuration;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import java.security.PrivilegedExceptionAction;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * Authenticates requests using Kerberos SPNEGO (HTTP Negotiate).
+ *
+ * <p>Uses the JDK's built-in GSSAPI/JGSS support – no external Kerberos 
library is required.
+ * The service principal and keytab must be configured via:
+ * <ul>
+ *   <li>{@code ranger.pdp.authn.kerberos.spnego.principal} – e.g. {@code 
HTTP/host.example.com@REALM}
+ *   <li>{@code ranger.pdp.authn.kerberos.spnego.keytab}    – absolute path to 
the keytab file
+ *   <li>{@code ranger.pdp.authn.kerberos.name.rules}       – Hadoop-style 
name rules (default: {@code DEFAULT})
+ * </ul>
+ *
+ * <p>Authentication flow:
+ * <ol>
+ *   <li>If no {@code Authorization: Negotiate} header is present the handler 
returns {@code SKIP}.
+ *   <li>The SPNEGO token is extracted, validated via GSSAPI, and – if a 
response token is
+ *       produced (mutual authentication) – written to {@code 
WWW-Authenticate: Negotiate <token>}.
+ *   <li>On success the short-form principal name (strip {@literal @REALM} and 
host components)
+ *       is returned as the authenticated user.
+ *   <li>On failure a {@code 401 Negotiate} challenge is sent and {@code 
CHALLENGE} is returned.
+ * </ol>
+ */
+public class KerberosAuthNHandler implements PdpAuthNHandler {
+    private static final Logger LOG = 
LoggerFactory.getLogger(KerberosAuthNHandler.class);
+
+    public static final String AUTH_TYPE = "KERBEROS";
+
+    private static final String NEGOTIATE_PREFIX    = "Negotiate ";
+    private static final String WWW_AUTHENTICATE    = "WWW-Authenticate";
+    private static final String AUTHORIZATION       = "Authorization";
+    private static final Oid    SPNEGO_OID;
+    private static final Oid    KRB5_OID;
+
+    static {
+        try {
+            SPNEGO_OID = new Oid("1.3.6.1.5.5.2");
+            KRB5_OID   = new Oid("1.2.840.113554.1.2.2");
+        } catch (GSSException e) {
+            throw new ExceptionInInitializerError(e);
+        }
+    }
+
+    private Subject       serviceSubject;
+    private GSSManager    gssManager;
+    private GSSCredential serverCred;
+
+    @Override
+    public void init(Properties config) throws Exception {
+        String principal     = 
config.getProperty(RangerPdpConstants.PROP_AUTHN_KERBEROS_SPNEGO_PRINCIPAL);
+        String keytab        = 
config.getProperty(RangerPdpConstants.PROP_AUTHN_KERBEROS_SPNEGO_KEYTAB);
+        String nameRules     = 
config.getProperty(RangerPdpConstants.PROP_AUTHN_KERBEROS_NAME_RULES, 
"DEFAULT");
+        String tokenValidity = 
config.getProperty(RangerPdpConstants.PROP_AUTHN_KERBEROS_KRB_TOKEN_VALIDITY);
+
+        int tokenLifetime = StringUtils.isBlank(tokenValidity) ? 
GSSCredential.INDEFINITE_LIFETIME : Integer.parseInt(tokenValidity);
+

Review Comment:
   `token.valid.seconds` is parsed with `Integer.parseInt()` without 
validation. A malformed value will throw `NumberFormatException` during filter 
init and effectively disable Kerberos auth (and can prevent the server from 
starting if no other handlers are enabled). Consider using a safe parse with a 
default/fallback (and an explicit log message) similar to 
`RangerPdpConfig#getInt()`.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to