[ 
https://issues.apache.org/jira/browse/KNOX-3426?focusedWorklogId=1039219&page=com.atlassian.jira.plugin.system.issuetabpanels:worklog-tabpanel#worklog-1039219
 ]

ASF GitHub Bot logged work on KNOX-3426:
----------------------------------------

                Author: ASF GitHub Bot
            Created on: 02/Sep/26 14:08
            Start Date: 02/Sep/26 14:08
    Worklog Time Spent: 10m 
      Work Description: smolnar82 commented on code in PR #1361:
URL: https://github.com/apache/knox/pull/1361#discussion_r3914975975


##########
gateway-server/src/main/java/org/apache/knox/gateway/services/knoxidf/delegation/JdbcDelegationPolicyService.java:
##########
@@ -0,0 +1,204 @@
+/*
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.knox.gateway.services.knoxidf.delegation;
+
+import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.database.DataSourceProvider;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.services.ServiceLifecycleException;
+import org.apache.knox.gateway.services.security.AliasService;
+
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * JDBC-backed implementation of {@link DelegationPolicyService}.
+ * Transaction management is handled by {@link DelegationPolicyDatabase}; this 
class
+ * is responsible for service lifecycle, exception translation, and evaluate() 
logic.
+ */
+public class JdbcDelegationPolicyService implements DelegationPolicyService {
+
+  private static final DelegationPolicyServiceMessages LOG =
+      MessagesFactory.get(DelegationPolicyServiceMessages.class);
+
+  private final AtomicBoolean initialized = new AtomicBoolean(false);
+  private final Lock initLock = new ReentrantLock(true);
+
+  private AliasService aliasService;
+  private DelegationPolicyDatabase database;
+  private int configuredKnoxTokenTtlSec;
+
+  @Override
+  public void init(GatewayConfig config, Map<String, String> options) throws 
ServiceLifecycleException {
+    if (!initialized.get()) {
+      initLock.lock();
+      try {
+        if (!initialized.get()) {
+          if (aliasService == null) {
+            throw new ServiceLifecycleException("The required AliasService 
reference has not been set.");
+          }
+          try {
+            this.configuredKnoxTokenTtlSec = 
config.getDelegationServiceTokenTtlSec();
+            this.database = new DelegationPolicyDatabase(
+                DataSourceProvider.getDataSource(config, aliasService),
+                config.getDatabaseType(),
+                config.getDelegationServiceListMaxTotal(),
+                config.getDelegationServiceListMaxPerAuthority());
+            initialized.set(true);
+          } catch (ServiceLifecycleException e) {
+            throw e;
+          } catch (Exception e) {
+            throw new ServiceLifecycleException("Error initializing 
JdbcDelegationPolicyService: " + e, e);
+          }
+        }
+      } finally {
+        initLock.unlock();
+      }
+    }
+  }
+
+  @Override
+  public void start() throws ServiceLifecycleException {
+  }
+
+  @Override
+  public void stop() throws ServiceLifecycleException {
+  }
+
+  public void setAliasService(AliasService aliasService) {
+    this.aliasService = aliasService;
+  }
+
+  @Override
+  public DelegationPolicy register(DelegationPolicy policy) {
+    try {
+      final String id = database.insertPolicy(policy);
+      return database.selectById(id).orElseThrow(
+          () -> new RuntimeException("Failed to read back registered policy " 
+ id));
+    } catch (Exception e) {
+      LOG.errorRegisteringPolicy(policy.getActorAuthority(), 
policy.getActorId(), e.getMessage(), e);
+      throw new RuntimeException(
+          "Error registering delegation policy for actor (" + 
policy.getActorAuthority() + ", " + policy.getActorId() + "): " + e, e);
+    }
+  }
+
+  @Override
+  public void update(String registrationId, DelegationPolicy policy) {
+    try {
+      database.updatePolicy(registrationId, policy);
+    } catch (Exception e) {
+      LOG.errorUpdatingPolicy(registrationId, e.getMessage(), e);
+      throw new RuntimeException("Error updating delegation policy " + 
registrationId + ": " + e, e);
+    }
+  }
+
+  @Override
+  public void delete(String registrationId) {
+    try {
+      database.deletePolicy(registrationId);
+    } catch (Exception e) {
+      LOG.errorDeletingPolicy(registrationId, e.getMessage(), e);
+      throw new RuntimeException("Error deleting delegation policy " + 
registrationId + ": " + e, e);
+    }
+  }
+
+  @Override
+  public Optional<DelegationPolicy> get(String registrationId) {
+    try {
+      return database.selectById(registrationId);
+    } catch (Exception e) {
+      LOG.errorReadingPolicy(registrationId, e.getMessage(), e);
+      throw new RuntimeException("Error reading delegation policy " + 
registrationId + ": " + e, e);
+    }
+  }
+
+  @Override
+  public Optional<DelegationPolicy> findByActor(String actorAuthority, String 
actorId) {
+    try {
+      return database.selectByActor(actorAuthority, actorId);
+    } catch (Exception e) {
+      LOG.errorListingPolicies(e.getMessage(), e);
+      throw new RuntimeException("Error looking up delegation policy for actor 
(" + actorAuthority + ", " + actorId + "): " + e, e);
+    }
+  }
+
+  @Override
+  public DelegationPolicyList list(String actorAuthorityFilter) {
+    try {
+      return database.selectAll(actorAuthorityFilter);
+    } catch (Exception e) {
+      LOG.errorListingPolicies(e.getMessage(), e);
+      throw new RuntimeException("Error listing delegation policies: " + e, e);
+    }
+  }
+
+  @Override
+  public PolicyDecision evaluate(PolicyCheckRequest request) {
+    // Step 1: look up registration
+    final Optional<DelegationPolicy> policyOpt = 
findByActor(request.getActorAuthority(), request.getActorId());
+    if (!policyOpt.isPresent()) {
+      return deny("actor_not_registered");
+    }
+    final DelegationPolicy policy = policyOpt.get();
+
+    // Step 2: headless exchange check
+    if (request.isHeadlessExchange() && !policy.isAllowHeadlessExchange()) {
+      return deny("headless_not_allowed");
+    }
+
+    // Step 3: user check (remember result; group check deferred to the end)
+    final boolean userCheckPassed =
+        policy.getCanActForUsers().contains(request.getSubjectName());
+
+    // Step 4: resource check
+    final Map<String, Set<String>> resourcePolicy = policy.getResourcePolicy();
+    if (!resourcePolicy.containsKey(request.getRequestedResource())) {
+      return deny("resource_not_allowed");
+    }
+
+    // Step 5: scope check (requested scopes are optional; when non-empty, all 
must be in the allowed set)
+    final Set<String> scopeSet = 
resourcePolicy.get(request.getRequestedResource());
+    if (!request.getRequestedScopes().isEmpty() && !scopeSet.isEmpty()
+        && !scopeSet.containsAll(request.getRequestedScopes())) {
+      return deny("scope_not_allowed");
+    }
+
+    // Step 6: effective TTL — use the policy value if set, otherwise fall 
back to the configured default
+    final int effectiveTtlSec = policy.getTokenTtlSec() != null
+        ? policy.getTokenTtlSec()
+        : configuredKnoxTokenTtlSec;
+
+    // Step 7: group check (LDAP lookup — slowest, deferred past cheap checks)
+    if (!userCheckPassed) {
+      if (!policy.getCanActForGroups().isEmpty()) {
+        throw new UnsupportedOperationException("canActFor.groups evaluation 
not yet implemented");

Review Comment:
   Claude's comment on this:
   ```
   evaluate() throws on a policy shape that register()/update() persists (there 
are even tests storing canActForGroups), 
   so this fires on a valid, stored policy — and per the design (KNOX-3426 
§6/§8) groups are a first-class MVP path, not an impossible input. 
   Once wired to the exchange handler this surfaces as 500 instead of the 
required access_denied.
   
   Either reject non-empty canActForGroups at registration (can't persist what 
you can't evaluate), or at least fail closed here — return 
deny("subject_not_allowed") — and defer the real LDAP check to a follow-up.
   ```





Issue Time Tracking
-------------------

    Worklog Id:     (was: 1039219)
    Time Spent: 3h 40m  (was: 3.5h)

> Delegation policy schema and JDBC implementation
> ------------------------------------------------
>
>                 Key: KNOX-3426
>                 URL: https://issues.apache.org/jira/browse/KNOX-3426
>             Project: Apache Knox
>          Issue Type: Task
>          Components: JWT
>            Reporter: Harrison Sheinblatt
>            Assignee: Harrison Sheinblatt
>            Priority: Major
>          Time Spent: 3h 40m
>  Remaining Estimate: 0h
>
> Persistent storage for delegation policies, with a service interface and JDBC 
> implementation. After these tasks, delegation policies can be stored and 
> retrieved.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to