Copilot commented on code in PR #4468:
URL: https://github.com/apache/streampark/pull/4468#discussion_r3698182351


##########
streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/system/authentication/AdminOnlyFilter.java:
##########
@@ -0,0 +1,74 @@
+/*
+ * 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.streampark.console.system.authentication;
+
+import org.apache.streampark.console.core.enums.UserType;
+import org.apache.streampark.console.system.entity.User;
+import org.apache.streampark.console.system.service.UserService;
+
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.authz.UnauthorizedException;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * A JWT filter variant that, on top of requiring a valid platform JWT 
(inherited from {@link
+ * JWTFilter}), additionally requires the authenticated user to be a platform 
administrator ({@link
+ * UserType#ADMIN}).
+ *
+ * <p>This exists to guard highly sensitive, non-REST endpoints (such as the 
embedded H2 database
+ * console) that cannot be protected with the usual method-level {@code 
@RequiresPermissions}
+ * annotation because they aren't Controller methods.
+ */
+@Slf4j
+@Component
+public class AdminOnlyFilter extends JWTFilter {
+
+  @Autowired private UserService userService;
+
+  @Override
+  protected boolean isAccessAllowed(
+      ServletRequest request, ServletResponse response, Object mappedValue)
+      throws UnauthorizedException {
+    boolean authenticated = super.isAccessAllowed(request, response, 
mappedValue);
+    if (!authenticated) {
+      return false;
+    }
+    try {
+      Long userId = JWTUtil.getUserId((String) 
SecurityUtils.getSubject().getPrincipal());
+      User user = userId == null ? null : userService.getById(userId);
+      if (user == null || user.getUserType() != UserType.ADMIN) {
+        log.warn(
+            "Denied non-admin access to an admin-only resource, userId={}, 
uri={}",
+            userId,
+            ((HttpServletRequest) request).getRequestURI());
+        return false;
+      }
+      return true;
+    } catch (Exception e) {
+      log.warn("Failed to verify admin-only access.", e);
+      return false;
+    }
+  }

Review Comment:
   `AdminOnlyFilter` inherits `JWTFilter` behavior that denies access unless an 
`Authorization` header is present (because `JWTFilter.isAccessAllowed` only 
calls `executeLogin` on header presence, otherwise returns `false`). For 
`/h2-console/**` this is likely to block legitimate admin use in a browser 
session (address-bar navigation won’t send the JWT header).
   
   Consider allowing access when the Shiro `Subject` is already authenticated 
(has a principal) and only falling back to JWT header login when no principal 
is present.



##########
streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/service/impl/ProxyServiceImpl.java:
##########
@@ -219,6 +222,42 @@ public void checkProxyAppLog(ApplicationLog log) {
     checkProxyApp(app);
   }
 
+  /**
+   * Verify the current user is allowed to access the proxied Web UI / REST 
API of the given Flink
+   * cluster. {@code t_flink_cluster} has no {@code team_id} column, so unlike 
{@link
+   * #checkProxyApp(Application)} this cannot compare against a real team id 
directly. Ownership is
+   * therefore derived indirectly: the platform administrator, the cluster's 
creator, or any user
+   * who belongs to a team that has at least one application bound to this 
cluster (via {@code
+   * Application#flinkClusterId}) is allowed through. Everyone else is denied.
+   *
+   * @param cluster the cluster being proxied, fetched from the database.
+   */
+  public void checkProxyCluster(FlinkCluster cluster) {
+    String token = serviceHelper.getAuthorization();
+    Long userId = token == null ? null : JWTUtil.getUserId(token);
+    if (userId == null) {
+      throw new PermissionDeniedException("Permission denied, please login 
first.");
+    }
+    if (userId.equals(cluster.getUserId())) {
+      return;
+    }
+    User user = userService.getById(userId);
+    if (user != null && user.getUserType() == UserType.ADMIN) {
+      return;
+    }
+    List<Team> userTeams = memberService.findUserTeams(userId);
+    boolean accessible =
+        userTeams.stream()
+            .anyMatch(
+                team ->
+                    applicationService.getByTeamId(team.getId()).stream()
+                        .anyMatch(app -> 
cluster.getId().equals(app.getFlinkClusterId())));
+    if (!accessible) {
+      throw new PermissionDeniedException(
+          "Permission denied, this cluster is not accessible from any team the 
current user belongs to.");
+    }

Review Comment:
   `checkProxyCluster` performs an N+1 pattern: it loads all teams for the 
user, then for each team calls `applicationService.getByTeamId(...)` and scans 
the full application list in memory. Because proxying the Flink UI typically 
triggers many asset/API requests, this can multiply DB queries and CPU work per 
page load.
   
   Consider collapsing this into a single DB query (or at least a single 
`count`) that checks whether *any* application exists with `flinkClusterId = 
cluster.id` and `teamId IN (userTeamIds)`.



-- 
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