CatalogAccessController.java

// 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.doris.mysql.privilege;

import org.apache.doris.analysis.ResourceTypeEnum;
import org.apache.doris.analysis.UserIdentity;
import org.apache.doris.authorization.DataMaskSpec;
import org.apache.doris.authorization.RowFilterSpec;
import org.apache.doris.common.AuthorizationException;

import java.util.List;
import java.util.Optional;
import java.util.Set;

/**
 * Decides access to the resources of one catalog, one kind of object at a time.
 *
 * <p>A controller is asked only about the resources it governs. The engine no longer establishes a global
 * privilege before routing, but an implementation of this interface is not thereby deprived of the one it
 * used to be handed: its scoped methods came in pairs, {@code checkDbPriv(boolean hasGlobal, ...)} in front
 * of {@code checkDbPriv(...)}, and {@link LegacyAccessControllerPlugin} still asks whoever governs instance
 * scope and grants on a yes, exactly as those default methods did. An implementation of the current contract
 * decides that exemption for itself instead - as the Ranger sources do, deferring to whichever source owns
 * global scope ({@code org.apache.doris.catalog.authorizer.ranger.RangerAccessController}, in the ranger
 * plugin and so not on this module's class path).
 *
 * <p>This is the older shape of that contract, kept because a catalog's {@code access_controller.class}
 * names an implementation of it and such implementations exist outside this repository. The engine reaches
 * one through {@link LegacyAccessControllerPlugin}; a source written today implements
 * {@link org.apache.doris.authorization.spi.AuthorizationPlugin} instead, which asks a single question about
 * a typed resource and answers by refusing rather than by returning false.
 *
 * <p><b>An implementation with data policies must be recompiled.</b> {@link #evalDataMaskPolicy} and
 * {@link #evalRowFilterPolicies} used to answer with {@code org.apache.doris.mysql.privilege.DataMaskPolicy}
 * and {@code org.apache.doris.mysql.privilege.RowFilterPolicy}; both types are gone, replaced by
 * {@link org.apache.doris.authorization.DataMaskSpec} and {@link org.apache.doris.authorization.RowFilterSpec}.
 * Both signatures erase to {@code Optional} and {@code List}, so a controller compiled against the old types
 * still loads and still answers every {@code check*Priv} - and then fails with {@code NoClassDefFoundError}
 * the first time a query reaches a table it holds a policy for. Both methods have always been abstract here,
 * so every implementation has one of each; what is unaffected is a controller whose two answers are always
 * empty, since nothing it returns names either type.
 *
 * @deprecated implement {@link org.apache.doris.authorization.spi.AuthorizationPlugin} instead. This
 *         interface still works and is still what {@code access_controller.class} may name, but it is not
 *         held to a plugin API version, so nothing detects when a Doris upgrade changes what it means.
 */
@Deprecated
public interface CatalogAccessController {
    default void close() {
    }

    // ==== Global ====
    boolean checkGlobalPriv(UserIdentity currentUser, PrivPredicate wanted);

    // ==== Catalog ====
    boolean checkCtlPriv(UserIdentity currentUser, String ctl, PrivPredicate wanted);

    // ==== Database ====
    boolean checkDbPriv(UserIdentity currentUser, String ctl, String db, PrivPredicate wanted);

    // ==== Table ====
    boolean checkTblPriv(UserIdentity currentUser, String ctl, String db, String tbl, PrivPredicate wanted);

    // ==== Resource ====
    boolean checkResourcePriv(UserIdentity currentUser, String resourceName, PrivPredicate wanted);

    // ==== Workload Group ====
    boolean checkWorkloadGroupPriv(UserIdentity currentUser, String workloadGroupName, PrivPredicate wanted);

    void checkColsPriv(UserIdentity currentUser, String ctl, String db, String tbl,
            Set<String> cols, PrivPredicate wanted) throws AuthorizationException;

    // ==== Cloud ====
    boolean checkCloudPriv(UserIdentity currentUser, String cloudName, PrivPredicate wanted, ResourceTypeEnum type);

    boolean checkStorageVaultPriv(UserIdentity currentUser, String storageVaultName, PrivPredicate wanted);

    /**
     * How {@code col} must be rewritten before {@code currentUser} may read it, or empty when it is not masked.
     * The returned payload carries a SQL expression, never a parsed one: see {@link DataMaskSpec}.
     */
    Optional<DataMaskSpec> evalDataMaskPolicy(UserIdentity currentUser, String ctl, String db, String tbl,
            String col);

    /**
     * The row-level filters that apply to {@code tbl} for {@code currentUser}, empty when there are none.
     * The engine combines them per {@link org.apache.doris.authorization.RowFilterMergeType}.
     */
    List<RowFilterSpec> evalRowFilterPolicies(UserIdentity currentUser, String ctl, String db, String tbl);
}