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