yifan-c commented on code in PR #179:
URL: https://github.com/apache/cassandra-sidecar/pull/179#discussion_r1941904341


##########
server/src/main/java/org/apache/cassandra/sidecar/acl/authorization/StandardPermission.java:
##########
@@ -47,13 +48,33 @@ public String name()
         return name;
     }
 
+    /**
+     * Sets resource scope for given permission.
+     *
+     * @param resourceScope scope of resource this permission checks or grants 
permission for.
+     * @return a reference to permission
+     */
+    public StandardPermission withScope(ResourceScope resourceScope)
+    {
+        this.resourceScope = resourceScope;
+        return this;
+    }
+
+    @Override
+    public ResourceScope resourceScope()
+    {
+        return resourceScope;
+    }
+
     @Override
     public Authorization toAuthorization(String resource)
     {
         PermissionBasedAuthorization authorization = 
PermissionBasedAuthorization.create(name);
         if (isNotEmpty(resource))
         {
-            authorization.setResource(resource);
+            ResourceScope resourceScope = resourceScope();
+            String resolvedResource = resourceScope != null ? 
resourceScope.resolveWithResource(resource) : resource;

Review Comment:
   Looks like all permissions declared do not have `resourceScope == null`. 
Maybe we can define the contract that all permissions should have non-null 
`resourceScope`? 



##########
server/src/main/java/org/apache/cassandra/sidecar/routes/cdc/ListCdcDirHandler.java:
##########
@@ -94,6 +93,14 @@ protected void handleInternal(RoutingContext context,
                                   Void request)
     {
         String cdcDir = metadataFetcher.instance(host).cdcDir();
+        if (isNullOrEmpty(cdcDir))
+        {
+            context.response()
+                   
.setStatusCode(HttpResponseStatus.SERVICE_UNAVAILABLE.code())
+                   .setStatusMessage("CDC not turned on for cluster")
+                   .end();
+            return;
+        }

Review Comment:
   I expressed the concern a few days ago at 
https://github.com/apache/cassandra-sidecar/pull/163#discussion_r1936764719. 
And it now became true :D
   
   So, we should not rely `null` value of `cdcDir` to decide cdc is not 
enabled. The `null` value only means the `cdcDir` is not configured. Server 
should only reply with "CDC directory is not configured in Sidecar". 



##########
server/src/main/java/org/apache/cassandra/sidecar/acl/authorization/StandardPermission.java:
##########
@@ -47,13 +48,33 @@ public String name()
         return name;
     }
 
+    /**
+     * Sets resource scope for given permission.
+     *
+     * @param resourceScope scope of resource this permission checks or grants 
permission for.
+     * @return a reference to permission
+     */
+    public StandardPermission withScope(ResourceScope resourceScope)
+    {
+        this.resourceScope = resourceScope;
+        return this;
+    }

Review Comment:
   This method leaves the created `StandardPermission` objects in the mutable 
state. The resourceScope of the permissions defined in `BasicPermissions` can 
be altered w/o notice. 
   
   I would suggest just change the constructor, since there are only 2 fields 
and remove the `withScope` method. Both fields now can be marked as `final`. 
   
   ```java
       protected final String name;
       protected final ResourceScope resourceScope;
   
       public StandardPermission(String name, ResourceScope resourceScope)
       {
           if (isNullOrEmpty(name))
           {
               throw new IllegalArgumentException("Permission name can not be 
null or empty");
           }
           this.name = name;
           this.resourceScope = resourceScope;
       }
   ```



##########
server/src/main/java/org/apache/cassandra/sidecar/acl/authorization/FeaturePermission.java:
##########
@@ -0,0 +1,130 @@
+/*
+ * 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.cassandra.sidecar.acl.authorization;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import io.vertx.ext.auth.authorization.WildcardPermissionBasedAuthorization;
+import 
io.vertx.ext.auth.authorization.impl.WildcardPermissionBasedAuthorizationImpl;
+
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.CREATE_RESTORE_JOB;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.CREATE_SNAPSHOT;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.DELETE_RESTORE_JOB;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.DELETE_SNAPSHOT;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.DELETE_STAGED_SSTABLE;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.EDIT_RESTORE_JOB;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.IMPORT_STAGED_SSTABLE;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.READ_GOSSIP;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.READ_RESTORE_JOB;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.READ_RING_KEYSPACE_SCOPED;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.READ_SCHEMA;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.READ_SCHEMA_KEYSPACE_SCOPED;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.READ_SNAPSHOT;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.READ_TOPOLOGY;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.STREAM_SNAPSHOT;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.UPLOAD_STAGED_SSTABLE;
+import static 
org.apache.cassandra.sidecar.acl.authorization.CassandraPermissions.MODIFY;
+import static 
org.apache.cassandra.sidecar.acl.authorization.CassandraPermissions.SELECT;
+import static 
org.apache.cassandra.sidecar.acl.authorization.ResourceScopes.TABLE;
+
+/**
+ * Enumerates a list of feature level permissions that Sidecar recognizes and 
honors.
+ */
+public enum FeaturePermission
+{
+    BULK_READ_DIRECT("ANALYTICS:READ_DIRECT",
+                     READ_RING_KEYSPACE_SCOPED,
+                     READ_SCHEMA_KEYSPACE_SCOPED,
+                     CREATE_SNAPSHOT,
+                     READ_SNAPSHOT,
+                     DELETE_SNAPSHOT,
+                     STREAM_SNAPSHOT,
+                     new StandardPermission(SELECT.name()).withScope(TABLE)),
+
+    BULK_WRITE_DIRECT("ANALYTICS:WRITE_DIRECT",
+                      READ_SCHEMA_KEYSPACE_SCOPED,
+                      READ_GOSSIP,
+                      READ_TOPOLOGY,
+                      UPLOAD_STAGED_SSTABLE,
+                      IMPORT_STAGED_SSTABLE,
+                      DELETE_STAGED_SSTABLE,
+                      new StandardPermission(MODIFY.name()).withScope(TABLE)),
+
+    BULK_WRITE_S3_COMPAT("ANALYTICS:WRITE_S3_COMPAT",
+                         READ_SCHEMA,
+                         READ_TOPOLOGY,
+                         CREATE_RESTORE_JOB,
+                         READ_RESTORE_JOB,
+                         EDIT_RESTORE_JOB,
+                         DELETE_RESTORE_JOB),
+
+    CDC("CDC", BasicPermissions.CDC);
+
+    // TODO make forbidden feature permission configurable
+    private static final Set<String> FORBIDDEN_FEATURE_PERMISSION = 
Collections.singleton("*:*");

Review Comment:
   It is to be addressed in this patch or a follow-up?



##########
server/src/main/java/org/apache/cassandra/sidecar/acl/authorization/DataResourceScope.java:
##########
@@ -0,0 +1,163 @@
+/*
+ * 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.cassandra.sidecar.acl.authorization;
+
+import java.util.Collections;
+import java.util.Set;
+
+import com.google.common.collect.ImmutableSet;
+
+import static org.apache.cassandra.sidecar.common.ApiEndpointsV1.KEYSPACE;
+import static org.apache.cassandra.sidecar.common.ApiEndpointsV1.TABLE;
+import static 
org.apache.cassandra.sidecar.common.utils.StringUtils.isNullOrEmpty;
+
+/**
+ * Signifies scope of Cassandra data resource. {@code keyspaceScoped} can be 
set to true to create data scope
+ * restricted to a keyspace. {@code tableScoped} can be set to true to create 
data scope restricted to a table.
+ */
+public class DataResourceScope implements ResourceScope
+{
+    private static final String DATA = "data";
+
+    /**
+     * Cassandra stores data resource in the format data, data/keyspace or 
data/keyspace_name/table_name within
+     * role_permissions table. A similar format is followed for storing data 
resources in sidecar permissions
+     * table role_permissions_v1. Hence, sidecar endpoints expect data 
resources to be provided in format
+     * data/keyspace_name/table_name.
+     * <p>
+     * In this context, curly braces are used to denote variable parts of the 
resource. For e.g., when permissions are
+     * checked for resource data/{keyspace} in an endpoint, the part within 
the curly braces ({keyspace})
+     * represents a placeholder for the actual keyspace name provided as a 
path parameter. For more context refer to
+     * io.vertx.ext.auth.authorization.impl.VariableAwareExpression
+     * <p>
+     * During the permission matching process, the placeholder {keyspace} is 
resolved to the actual keyspace
+     * being accessed by the endpoint. For e.g. data/{keyspace} resolves to 
data/university if the keyspace is
+     * "university".
+     * <p>
+     * User permissions are then extracted from both Cassandra and sidecar 
role permissions tables for
+     * the resolved resource and are matched against the expected permissions 
set defined in the endpoint's handler.
+     */
+    private static final String DATA_WITH_KEYSPACE = 
String.format("data/{%s}", KEYSPACE);
+
+    // TODO remove this hack once VariableAwareExpression bug is fixed
+    // VariableAwareExpression in vertx-auth-common package has a bug during 
String.substring() call, hence
+    // we cannot set resources that do not end in curly braces (e.g. 
data/keyspace/*) in
+    // PermissionBasedAuthorizationImpl or 
WildcardPermissionBasedAuthorizationImpl. data/{%s}/{TABLE_WILDCARD} treats
+    // TABLE_WILDCARD as a variable. This hack allows to read resource level 
permissions that could be set for all
+    // tables through data/<keyspace_name>/*. Bug should be fixed in 4.5.12
+    // Note: DATA_WITH_KEYSPACE_ALL_TABLES authorizes for all tables under the 
keyspace excluding the keyspace itself
+    private static final String DATA_WITH_KEYSPACE_ALL_TABLES = 
String.format("data/{%s}/{TABLE_WILDCARD}", KEYSPACE);
+
+    private static final String DATA_WITH_KEYSPACE_TABLE = 
String.format("data/{%s}/{%s}", KEYSPACE, TABLE);
+
+    private final boolean keyspaceScoped;
+    private final boolean tableScoped;
+    private final Set<String> expandedResources;
+
+    private DataResourceScope(boolean keyspaceScoped, boolean tableScoped)
+    {
+        this.keyspaceScoped = keyspaceScoped;
+        this.tableScoped = tableScoped;
+        this.expandedResources = initializeExpandedResources();
+    }
+
+    private Set<String> initializeExpandedResources()
+    {
+        if (tableScoped)
+        {
+            // can expand to DATA, DATA_WITH_KEYSPACE and 
DATA_WITH_KEYSPACE_ALL_TABLES
+            return ImmutableSet.of(DATA, DATA_WITH_KEYSPACE, 
DATA_WITH_KEYSPACE_ALL_TABLES, DATA_WITH_KEYSPACE_TABLE);
+        }
+        if (keyspaceScoped)
+        {
+            // can expand to DATA
+            return ImmutableSet.of(DATA, DATA_WITH_KEYSPACE);
+        }
+        return Collections.singleton(DATA);
+    }
+
+    @Override
+    public String variableAwareResource()
+    {
+        if (tableScoped)
+        {
+            return DATA_WITH_KEYSPACE_TABLE;
+        }
+        if (keyspaceScoped)
+        {
+            return DATA_WITH_KEYSPACE;
+        }
+        return DATA;
+    }
+
+    @Override
+    public String resolveWithResource(String resource)
+    {
+        if (isNullOrEmpty(resource))
+        {
+            throw new IllegalArgumentException("Resource expected for 
resolving");
+        }
+
+        if (!resource.startsWith("data"))
+        {
+            return variableAwareResource();
+        }
+
+        String[] parts = resource.split("/");
+        if (tableScoped)
+        {
+            return resource;
+        }
+        else if (keyspaceScoped)
+        {
+            return parts.length == 3 ? "data/" + parts[1] : resource;
+        }
+        return "data";
+    }
+
+    @Override
+    public Set<String> expandedResources()
+    {
+        return expandedResources;
+    }
+
+    /**
+     * @return {@link DataResourceScope} scoped at data level including all 
keyspaces and tables
+     */
+    public static DataResourceScope createWithDataScope()
+    {
+        return new DataResourceScope(false, false);
+    }
+
+    /**
+     * @return {@link DataResourceScope} scoped at keyspace level within data
+     */
+    public static DataResourceScope createWithKeyspaceScope()
+    {
+        return new DataResourceScope(true, false);
+    }
+
+    /**
+     * @return {@link DataResourceScope} scoped at table level within a 
keyspace
+     */
+    public static DataResourceScope createWithTableScope()

Review Comment:
   nit: maybe create the static instances directly, instead of expose static 
methods. 



##########
server/src/main/java/org/apache/cassandra/sidecar/acl/authorization/FeaturePermission.java:
##########
@@ -0,0 +1,130 @@
+/*
+ * 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.cassandra.sidecar.acl.authorization;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import io.vertx.ext.auth.authorization.WildcardPermissionBasedAuthorization;
+import 
io.vertx.ext.auth.authorization.impl.WildcardPermissionBasedAuthorizationImpl;
+
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.CREATE_RESTORE_JOB;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.CREATE_SNAPSHOT;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.DELETE_RESTORE_JOB;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.DELETE_SNAPSHOT;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.DELETE_STAGED_SSTABLE;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.EDIT_RESTORE_JOB;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.IMPORT_STAGED_SSTABLE;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.READ_GOSSIP;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.READ_RESTORE_JOB;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.READ_RING_KEYSPACE_SCOPED;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.READ_SCHEMA;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.READ_SCHEMA_KEYSPACE_SCOPED;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.READ_SNAPSHOT;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.READ_TOPOLOGY;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.STREAM_SNAPSHOT;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.UPLOAD_STAGED_SSTABLE;
+import static 
org.apache.cassandra.sidecar.acl.authorization.CassandraPermissions.MODIFY;
+import static 
org.apache.cassandra.sidecar.acl.authorization.CassandraPermissions.SELECT;
+import static 
org.apache.cassandra.sidecar.acl.authorization.ResourceScopes.TABLE;
+
+/**
+ * Enumerates a list of feature level permissions that Sidecar recognizes and 
honors.
+ */
+public enum FeaturePermission
+{
+    BULK_READ_DIRECT("ANALYTICS:READ_DIRECT",

Review Comment:
   Can the enum match with the `name`? The enum is `BULK_READ_DIRECT`, but the 
name has `ANALYTICS`



##########
server/src/main/java/org/apache/cassandra/sidecar/acl/authorization/DataResourceScope.java:
##########
@@ -0,0 +1,163 @@
+/*
+ * 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.cassandra.sidecar.acl.authorization;
+
+import java.util.Collections;
+import java.util.Set;
+
+import com.google.common.collect.ImmutableSet;
+
+import static org.apache.cassandra.sidecar.common.ApiEndpointsV1.KEYSPACE;
+import static org.apache.cassandra.sidecar.common.ApiEndpointsV1.TABLE;
+import static 
org.apache.cassandra.sidecar.common.utils.StringUtils.isNullOrEmpty;
+
+/**
+ * Signifies scope of Cassandra data resource. {@code keyspaceScoped} can be 
set to true to create data scope
+ * restricted to a keyspace. {@code tableScoped} can be set to true to create 
data scope restricted to a table.
+ */
+public class DataResourceScope implements ResourceScope
+{
+    private static final String DATA = "data";
+
+    /**
+     * Cassandra stores data resource in the format data, data/keyspace or 
data/keyspace_name/table_name within
+     * role_permissions table. A similar format is followed for storing data 
resources in sidecar permissions
+     * table role_permissions_v1. Hence, sidecar endpoints expect data 
resources to be provided in format
+     * data/keyspace_name/table_name.
+     * <p>
+     * In this context, curly braces are used to denote variable parts of the 
resource. For e.g., when permissions are
+     * checked for resource data/{keyspace} in an endpoint, the part within 
the curly braces ({keyspace})
+     * represents a placeholder for the actual keyspace name provided as a 
path parameter. For more context refer to
+     * io.vertx.ext.auth.authorization.impl.VariableAwareExpression
+     * <p>
+     * During the permission matching process, the placeholder {keyspace} is 
resolved to the actual keyspace
+     * being accessed by the endpoint. For e.g. data/{keyspace} resolves to 
data/university if the keyspace is
+     * "university".
+     * <p>
+     * User permissions are then extracted from both Cassandra and sidecar 
role permissions tables for
+     * the resolved resource and are matched against the expected permissions 
set defined in the endpoint's handler.
+     */
+    private static final String DATA_WITH_KEYSPACE = 
String.format("data/{%s}", KEYSPACE);
+
+    // TODO remove this hack once VariableAwareExpression bug is fixed
+    // VariableAwareExpression in vertx-auth-common package has a bug during 
String.substring() call, hence
+    // we cannot set resources that do not end in curly braces (e.g. 
data/keyspace/*) in
+    // PermissionBasedAuthorizationImpl or 
WildcardPermissionBasedAuthorizationImpl. data/{%s}/{TABLE_WILDCARD} treats
+    // TABLE_WILDCARD as a variable. This hack allows to read resource level 
permissions that could be set for all
+    // tables through data/<keyspace_name>/*. Bug should be fixed in 4.5.12
+    // Note: DATA_WITH_KEYSPACE_ALL_TABLES authorizes for all tables under the 
keyspace excluding the keyspace itself
+    private static final String DATA_WITH_KEYSPACE_ALL_TABLES = 
String.format("data/{%s}/{TABLE_WILDCARD}", KEYSPACE);
+
+    private static final String DATA_WITH_KEYSPACE_TABLE = 
String.format("data/{%s}/{%s}", KEYSPACE, TABLE);
+
+    private final boolean keyspaceScoped;
+    private final boolean tableScoped;
+    private final Set<String> expandedResources;
+
+    private DataResourceScope(boolean keyspaceScoped, boolean tableScoped)
+    {
+        this.keyspaceScoped = keyspaceScoped;
+        this.tableScoped = tableScoped;
+        this.expandedResources = initializeExpandedResources();
+    }
+
+    private Set<String> initializeExpandedResources()
+    {
+        if (tableScoped)
+        {
+            // can expand to DATA, DATA_WITH_KEYSPACE and 
DATA_WITH_KEYSPACE_ALL_TABLES
+            return ImmutableSet.of(DATA, DATA_WITH_KEYSPACE, 
DATA_WITH_KEYSPACE_ALL_TABLES, DATA_WITH_KEYSPACE_TABLE);
+        }
+        if (keyspaceScoped)
+        {
+            // can expand to DATA
+            return ImmutableSet.of(DATA, DATA_WITH_KEYSPACE);
+        }
+        return Collections.singleton(DATA);
+    }
+
+    @Override
+    public String variableAwareResource()
+    {
+        if (tableScoped)
+        {
+            return DATA_WITH_KEYSPACE_TABLE;
+        }
+        if (keyspaceScoped)
+        {
+            return DATA_WITH_KEYSPACE;
+        }
+        return DATA;
+    }
+
+    @Override
+    public String resolveWithResource(String resource)
+    {
+        if (isNullOrEmpty(resource))
+        {
+            throw new IllegalArgumentException("Resource expected for 
resolving");
+        }
+
+        if (!resource.startsWith("data"))
+        {
+            return variableAwareResource();
+        }

Review Comment:
   Should it throw `IllegalArgumentException` too? 
   For example, what if the `resource` in the database row is somehow defined 
as "foo". It seems to me that "foo" is an invalid definition. For data 
resource, can we enforce the format to be `data[/keyspace[/table]]`



##########
server/src/main/java/org/apache/cassandra/sidecar/acl/authorization/DataResourceScope.java:
##########
@@ -0,0 +1,163 @@
+/*
+ * 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.cassandra.sidecar.acl.authorization;
+
+import java.util.Collections;
+import java.util.Set;
+
+import com.google.common.collect.ImmutableSet;
+
+import static org.apache.cassandra.sidecar.common.ApiEndpointsV1.KEYSPACE;
+import static org.apache.cassandra.sidecar.common.ApiEndpointsV1.TABLE;
+import static 
org.apache.cassandra.sidecar.common.utils.StringUtils.isNullOrEmpty;
+
+/**
+ * Signifies scope of Cassandra data resource. {@code keyspaceScoped} can be 
set to true to create data scope
+ * restricted to a keyspace. {@code tableScoped} can be set to true to create 
data scope restricted to a table.
+ */
+public class DataResourceScope implements ResourceScope
+{
+    private static final String DATA = "data";
+
+    /**
+     * Cassandra stores data resource in the format data, data/keyspace or 
data/keyspace_name/table_name within
+     * role_permissions table. A similar format is followed for storing data 
resources in sidecar permissions
+     * table role_permissions_v1. Hence, sidecar endpoints expect data 
resources to be provided in format
+     * data/keyspace_name/table_name.
+     * <p>
+     * In this context, curly braces are used to denote variable parts of the 
resource. For e.g., when permissions are
+     * checked for resource data/{keyspace} in an endpoint, the part within 
the curly braces ({keyspace})
+     * represents a placeholder for the actual keyspace name provided as a 
path parameter. For more context refer to
+     * io.vertx.ext.auth.authorization.impl.VariableAwareExpression
+     * <p>
+     * During the permission matching process, the placeholder {keyspace} is 
resolved to the actual keyspace
+     * being accessed by the endpoint. For e.g. data/{keyspace} resolves to 
data/university if the keyspace is
+     * "university".
+     * <p>
+     * User permissions are then extracted from both Cassandra and sidecar 
role permissions tables for
+     * the resolved resource and are matched against the expected permissions 
set defined in the endpoint's handler.
+     */
+    private static final String DATA_WITH_KEYSPACE = 
String.format("data/{%s}", KEYSPACE);
+
+    // TODO remove this hack once VariableAwareExpression bug is fixed
+    // VariableAwareExpression in vertx-auth-common package has a bug during 
String.substring() call, hence
+    // we cannot set resources that do not end in curly braces (e.g. 
data/keyspace/*) in
+    // PermissionBasedAuthorizationImpl or 
WildcardPermissionBasedAuthorizationImpl. data/{%s}/{TABLE_WILDCARD} treats
+    // TABLE_WILDCARD as a variable. This hack allows to read resource level 
permissions that could be set for all
+    // tables through data/<keyspace_name>/*. Bug should be fixed in 4.5.12
+    // Note: DATA_WITH_KEYSPACE_ALL_TABLES authorizes for all tables under the 
keyspace excluding the keyspace itself
+    private static final String DATA_WITH_KEYSPACE_ALL_TABLES = 
String.format("data/{%s}/{TABLE_WILDCARD}", KEYSPACE);
+
+    private static final String DATA_WITH_KEYSPACE_TABLE = 
String.format("data/{%s}/{%s}", KEYSPACE, TABLE);
+
+    private final boolean keyspaceScoped;
+    private final boolean tableScoped;
+    private final Set<String> expandedResources;

Review Comment:
   ok. I guess order does not matter here.



##########
server/src/main/java/org/apache/cassandra/sidecar/routes/cdc/ListCdcDirHandler.java:
##########
@@ -102,7 +109,7 @@ protected void handleInternal(RoutingContext context,
             LOGGER.warn("Error listing the CDC commit log segments", cause);
             context.response()
                    
.setStatusCode(HttpResponseStatus.INTERNAL_SERVER_ERROR.code())
-                   .setStatusMessage(cause.getMessage())
+                   .setStatusMessage(cause != null ? cause.getMessage() : 
"Error while listing CDC segments")

Review Comment:
   Do you see response message with 'null'?



##########
server/src/main/java/org/apache/cassandra/sidecar/acl/authorization/FeaturePermission.java:
##########
@@ -0,0 +1,130 @@
+/*
+ * 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.cassandra.sidecar.acl.authorization;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import io.vertx.ext.auth.authorization.WildcardPermissionBasedAuthorization;
+import 
io.vertx.ext.auth.authorization.impl.WildcardPermissionBasedAuthorizationImpl;
+
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.CREATE_RESTORE_JOB;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.CREATE_SNAPSHOT;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.DELETE_RESTORE_JOB;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.DELETE_SNAPSHOT;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.DELETE_STAGED_SSTABLE;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.EDIT_RESTORE_JOB;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.IMPORT_STAGED_SSTABLE;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.READ_GOSSIP;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.READ_RESTORE_JOB;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.READ_RING_KEYSPACE_SCOPED;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.READ_SCHEMA;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.READ_SCHEMA_KEYSPACE_SCOPED;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.READ_SNAPSHOT;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.READ_TOPOLOGY;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.STREAM_SNAPSHOT;
+import static 
org.apache.cassandra.sidecar.acl.authorization.BasicPermissions.UPLOAD_STAGED_SSTABLE;
+import static 
org.apache.cassandra.sidecar.acl.authorization.CassandraPermissions.MODIFY;
+import static 
org.apache.cassandra.sidecar.acl.authorization.CassandraPermissions.SELECT;
+import static 
org.apache.cassandra.sidecar.acl.authorization.ResourceScopes.TABLE;
+
+/**
+ * Enumerates a list of feature level permissions that Sidecar recognizes and 
honors.
+ */
+public enum FeaturePermission
+{
+    BULK_READ_DIRECT("ANALYTICS:READ_DIRECT",
+                     READ_RING_KEYSPACE_SCOPED,
+                     READ_SCHEMA_KEYSPACE_SCOPED,
+                     CREATE_SNAPSHOT,
+                     READ_SNAPSHOT,
+                     DELETE_SNAPSHOT,
+                     STREAM_SNAPSHOT,
+                     new StandardPermission(SELECT.name()).withScope(TABLE)),
+
+    BULK_WRITE_DIRECT("ANALYTICS:WRITE_DIRECT",
+                      READ_SCHEMA_KEYSPACE_SCOPED,
+                      READ_GOSSIP,
+                      READ_TOPOLOGY,
+                      UPLOAD_STAGED_SSTABLE,
+                      IMPORT_STAGED_SSTABLE,
+                      DELETE_STAGED_SSTABLE,
+                      new StandardPermission(MODIFY.name()).withScope(TABLE)),
+
+    BULK_WRITE_S3_COMPAT("ANALYTICS:WRITE_S3_COMPAT",
+                         READ_SCHEMA,
+                         READ_TOPOLOGY,
+                         CREATE_RESTORE_JOB,
+                         READ_RESTORE_JOB,
+                         EDIT_RESTORE_JOB,
+                         DELETE_RESTORE_JOB),
+
+    CDC("CDC", BasicPermissions.CDC);
+
+    // TODO make forbidden feature permission configurable
+    private static final Set<String> FORBIDDEN_FEATURE_PERMISSION = 
Collections.singleton("*:*");
+
+    private static final List<FeaturePermission> FEATURE_PERMISSIONS
+    = Arrays.stream(values()).collect(Collectors.toList());
+
+    private final WildcardPermissionBasedAuthorization nameAuthorization;
+    private final CompositePermission permission;
+
+    FeaturePermission(String name, Permission... permissions)
+    {
+        this.nameAuthorization = new 
WildcardPermissionBasedAuthorizationImpl(name);
+        this.permission
+        = new CompositePermission(name, 
Arrays.stream(permissions).collect(Collectors.toList()));
+    }
+
+    public Permission permission()
+    {
+        return permission;
+    }
+
+    /**
+     * Returns a {@link CompositePermission} if a feature level permission 
match is found for given permission name.
+     * {@link CompositePermission} consists of all basic permissions 
associated with given feature level permission.
+     *
+     * @param name permission name
+     * @return a {@link CompositePermission} containing the basic permissions 
that correspond to given feature level
+     *         permission, or {@code null} if no match is found.
+     */
+    public static Permission fromName(String name)
+    {
+        if (FORBIDDEN_FEATURE_PERMISSION.contains(name))
+        {
+            return null;
+        }
+        List<Permission> combinedPermissions = new ArrayList<>();
+        WildcardPermissionBasedAuthorization requestedAuthorization = new 
WildcardPermissionBasedAuthorizationImpl(name);
+        for (FeaturePermission featurePermission : FEATURE_PERMISSIONS)
+        {
+            if 
(requestedAuthorization.verify(featurePermission.nameAuthorization))

Review Comment:
   Is it to support this scenario, for example, `name == "Analytics:*"`? 



-- 
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: pr-unsubscr...@cassandra.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: pr-unsubscr...@cassandra.apache.org
For additional commands, e-mail: pr-h...@cassandra.apache.org


Reply via email to