All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.clevercloud.biscuitpulsar.AuthorizationProviderBiscuit Maven / Gradle / Ivy

There is a newer version: 3.7.1
Show newest version
package com.clevercloud.biscuitpulsar;

import com.clevercloud.biscuit.error.Error;
import com.clevercloud.biscuit.token.Biscuit;
import com.clevercloud.biscuit.token.Verifier;
import com.clevercloud.biscuit.token.builder.Caveat;
import com.clevercloud.biscuit.token.builder.Fact;
import com.clevercloud.biscuit.token.builder.Predicate;
import io.vavr.control.Either;
import org.apache.pulsar.broker.ServiceConfiguration;
import org.apache.pulsar.broker.authentication.AuthenticationDataSource;
import org.apache.pulsar.broker.authorization.AuthorizationProvider;
import org.apache.pulsar.broker.authorization.PulsarAuthorizationProvider;
import org.apache.pulsar.broker.cache.ConfigurationCacheService;
import org.apache.pulsar.common.naming.NamespaceName;
import org.apache.pulsar.common.naming.TopicName;
import org.apache.pulsar.common.policies.data.AuthAction;
import org.apache.pulsar.common.policies.data.NamespaceOperation;
import org.apache.pulsar.common.policies.data.PolicyName;
import org.apache.pulsar.common.policies.data.PolicyOperation;
import org.apache.pulsar.common.policies.data.TenantOperation;
import org.apache.pulsar.common.policies.data.TopicOperation;
import org.apache.pulsar.common.util.FutureUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.Arrays;
import java.util.Base64;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;

import static com.clevercloud.biscuit.token.builder.Utils.*;
import static io.vavr.API.Left;
import static io.vavr.API.Right;

public class AuthorizationProviderBiscuit implements AuthorizationProvider {
    private static final Logger log = LoggerFactory.getLogger(AuthorizationProviderBiscuit.class);

    public ServiceConfiguration conf;
    public ConfigurationCacheService configCache;
    private PulsarAuthorizationProvider defaultProvider;

    public AuthorizationProviderBiscuit() {
    }

    public AuthorizationProviderBiscuit(ServiceConfiguration conf, ConfigurationCacheService configCache)
            throws IOException {
        initialize(conf, configCache);
    }

    private Fact topic(TopicName topicName) {
        return fact("topic", Arrays.asList(s("ambient"), string(topicName.getTenant()), string(topicName.getNamespacePortion()), string(topicName.getLocalName())));
    }

    private Fact subscription(TopicName topicName, String subscription) {
        return fact("subscription", Arrays.asList(s("ambient"), string(topicName.getTenant()), string(topicName.getNamespacePortion()), string(topicName.getLocalName()), string(subscription)));
    }

    private Predicate topicRight(TopicName topicName, String right) {
        return pred("right", Arrays.asList(s("authority"), s("topic"),
                string(topicName.getTenant()), string(topicName.getNamespacePortion()), string(topicName.getLocalName()), s(right)));
    }

    private Fact namespace(NamespaceName namespaceName) {
        return fact("namespace", Arrays.asList(s("ambient"), string(namespaceName.getTenant()), string(namespaceName.getLocalName())));
    }

    private Predicate namespaceOperationRight(NamespaceName namespaceName, String right) {
        return pred("right", Arrays.asList(s("authority"), s("namespace"),
                string(namespaceName.getTenant()), string(namespaceName.getLocalName()), s(right)));
    }

    private Predicate topicSubscriptionRight(TopicName topicName, String subscription, String right) {
        return pred("right", Arrays.asList(s("authority"), s("topic"),
                string(topicName.getTenant()), string(topicName.getNamespacePortion()), string(topicName.getLocalName()), s(right), string(subscription)));
    }

    public Either verifierFromBiscuit(String role) {
        Either deser = Biscuit.from_sealed(
                Base64.getDecoder().decode(role.substring("biscuit:".length())),
                AuthenticationProviderBiscuit.SEALING_KEY.getBytes()
        );
        if (deser.isLeft()) {
            Error e = deser.getLeft();
            log.error(e.toString());
            return Left(e);
        }

        Biscuit token = deser.get();
        Either res = token.verify_sealed();

        if (res.isLeft()) {
            return res;
        }

        Verifier verifier = res.get();
        verifier.add_rule(rule("right", Arrays.asList(s("authority"), s("topic"), var(0), var(1), var(2), s("lookup")),
                Arrays.asList(pred("right", Arrays.asList(s("authority"), s("topic"), var(0), var(1), var(2), s("produce"))))));

        verifier.add_rule(rule("right", Arrays.asList(s("authority"), s("topic"), var(0), var(1), var(2), s("lookup")),
                Arrays.asList(pred("right", Arrays.asList(s("authority"), s("topic"), var(0), var(1), var(2), s("consume"))))));

        verifier.add_rule(rule("right", Arrays.asList(s("authority"), s("topic"), var(0), var(1), var(2), s("lookup")),
                Arrays.asList(pred("right", Arrays.asList(s("authority"), s("topic"), var(0), var(1), var(2), s("consume"), var(3))))));

        verifier.add_rule(rule("right", Arrays.asList(s("authority"), s("topic"), var(0), var(1), var(2), s("produce")),
                Arrays.asList(
                        pred("right", Arrays.asList(s("authority"), s("namespace"), var(0), var(1), s("produce"))),
                        pred("topic", Arrays.asList(s("ambient"), var(0), var(1), var(2)))
                )));
        verifier.add_rule(rule("right", Arrays.asList(s("authority"), s("topic"), var(0), var(1), var(2), s("consume")),
                Arrays.asList(
                        pred("right", Arrays.asList(s("authority"), s("namespace"), var(0), var(1), s("consume"))),
                        pred("topic", Arrays.asList(s("ambient"), var(0), var(1), var(2))))));

        verifier.add_rule(rule("right", Arrays.asList(s("authority"), s("topic"), var(0), var(1), var(2), s("consume"), var(3)),
                Arrays.asList(
                        pred("right", Arrays.asList(s("authority"), s("namespace"), var(0), var(1), s("consume"))),
                        pred("topic", Arrays.asList(s("ambient"), var(0), var(1), var(2))),
                        pred("subscription", Arrays.asList(s("ambient"), var(0), var(1), var(2), var(3))))));

        verifier.add_rule(rule("right", Arrays.asList(s("authority"), s("topic"), var(0), var(1), var(2), s("produce")),
                Arrays.asList(
                        pred("right", Arrays.asList(s("authority"), s("admin"))),
                        pred("topic", Arrays.asList(s("ambient"), var(0), var(1), var(2))))));

        verifier.add_rule(rule("right", Arrays.asList(s("authority"), s("topic"), var(0), var(1), var(2), s("consume")),
                Arrays.asList(
                        pred("right", Arrays.asList(s("authority"), s("admin"))),
                        pred("topic", Arrays.asList(s("ambient"), var(0), var(1), var(2))))));

        verifier.add_rule(rule("right", Arrays.asList(s("authority"), s("topic"), var(0), var(1), var(2), s("consume"), var(1)),
                Arrays.asList(
                        pred("right", Arrays.asList(s("authority"), s("admin"))),
                        pred("topic", Arrays.asList(s("ambient"), var(0), var(1), var(2))),
                        pred("subscription", Arrays.asList(s("ambient"), var(0), var(1), var(2), var(3)))
                )));

        //*check_right(#authority, #namespace, $0, $1, $2) <- !ns_operation(#authority, #namespace, $0, $1, $2), right(#authority, #namespace, $0, $1, $2) et `*check_right(#authority, #topic, $0, $1, $2, $3) <- !topic_operation(#authority, #topic, $0, $1, $2, $3), right(#authority, #namespace, $0, $1, $2, $3)

        //log.debug(verifier.print_world());

        return Right(verifier);
    }

    @Override
    public void initialize(ServiceConfiguration conf, ConfigurationCacheService configCache) throws IOException {
        this.conf = conf;
        this.configCache = configCache;
        defaultProvider = new PulsarAuthorizationProvider(conf, configCache);
    }

    @Override
    public CompletableFuture canProduceAsync(TopicName topicName, String role, AuthenticationDataSource authenticationData) {
        if (!role.startsWith("biscuit:")) {
            return defaultProvider.canProduceAsync(topicName, role, authenticationData);
        }

        CompletableFuture permissionFuture = new CompletableFuture<>();

        Either res = verifierFromBiscuit(role);
        if (res.isLeft()) {
            log.error("could not create verifier {}", res.getLeft().toString());
            permissionFuture.complete(false);
            return permissionFuture;
        }

        Verifier verifier = res.get();
        //verifier.set_time();

        verifier.add_fact(fact("topic_operation",
                Arrays.asList(s("ambient"), string(topicName.getTenant()), string(topicName.getNamespacePortion()), string(topicName.getLocalName()), s("produce"))));
        verifier.add_fact(fact("topic",
                Arrays.asList(s("ambient"), string(topicName.getTenant()), string(topicName.getNamespacePortion()), string(topicName.getLocalName()))));
        verifier.add_caveat(new Caveat(Arrays.asList(
                rule("check_right",
                        Arrays.asList(),
                        Arrays.asList(pred("right",
                                Arrays.asList(s("authority"), string(topicName.getTenant()), string(topicName.getNamespacePortion()), string(topicName.getLocalName()), s("produce"))))
                ))));

        log.debug(verifier.print_world());
        Either verifierResult = verifier.verify();
        if (verifierResult.isLeft()) {
            log.error("produce verifier failure: {}", verifierResult.getLeft());
        } else {
            log.debug("produce request authorized by biscuit token");
        }

        permissionFuture.complete(verifierResult.isRight());

        return permissionFuture;
    }

    @Override
    public CompletableFuture canConsumeAsync(TopicName topicName, String role, AuthenticationDataSource authenticationData, String subscription) {
        if (!role.startsWith("biscuit:")) {
            return defaultProvider.canConsumeAsync(topicName, role, authenticationData, subscription);
        }

        CompletableFuture permissionFuture = new CompletableFuture<>();

        Either res = verifierFromBiscuit(role);
        if (res.isLeft()) {
            permissionFuture.complete(false);
            return permissionFuture;
        }

        Verifier verifier = res.get();
        verifier.add_fact(fact("topic_operation",
                Arrays.asList(s("ambient"), string(topicName.getTenant()), string(topicName.getNamespacePortion()), string(topicName.getLocalName()), s("consume"))));
        verifier.add_fact(fact("topic",
                Arrays.asList(s("ambient"), string(topicName.getTenant()), string(topicName.getNamespacePortion()), string(topicName.getLocalName()))));
        verifier.add_caveat(new Caveat(Arrays.asList(
                rule("check_right",
                        Arrays.asList(),
                        Arrays.asList(pred("right",
                                Arrays.asList(s("authority"), string(topicName.getTenant()), string(topicName.getNamespacePortion()), string(topicName.getLocalName()), s("consume"))))
                ))));

        // add these rules because there are two ways to verify that we can consume: with a right defined on the topic
        // or one defined on the subscription
        /*verifier.add_rule(rule("can_consume", Arrays.asList(s("authority"), s("topic"), string(topicName.getTenant()), string(topicName.getNamespacePortion()), string(topicName.getLocalName())),
                Arrays.asList(
                        topicSubscriptionRight(topicName, subscription, "consume"))));

        verifier.add_rule(rule("can_consume", Arrays.asList(s("authority"), s("topic"), string(topicName.getTenant()), string(topicName.getNamespacePortion()), string(topicName.getLocalName())),
                Arrays.asList(
                        topicRight(topicName, "consume"))));

        verifier.add_caveat(caveat(rule(
                "checked_consume_right",
                Arrays.asList(s("topic"), string(topicName.getTenant()), string(topicName.getNamespacePortion()), string(topicName.getLocalName()), s("consume")),
                Arrays.asList(
                        pred("can_consume", Arrays.asList(s("authority"), s("topic"), string(topicName.getTenant()), string(topicName.getNamespacePortion()), string(topicName.getLocalName())))
                )
        )));*/

        Either deser = Biscuit.from_sealed(
                Base64.getDecoder().decode(role.substring("biscuit:".length())),
                AuthenticationProviderBiscuit.SEALING_KEY.getBytes()
        );
        if (deser.isLeft()) {
            Error e = deser.getLeft();
            log.error(e.toString());
        }

        log.debug(verifier.print_world());
        Either verifierResult = verifier.verify();
        if (verifierResult.isLeft()) {
            log.error("consume verifier failure: {}", verifierResult.getLeft());
        } else {
            log.debug("consume request authorized by biscuit token");
        }

        permissionFuture.complete(verifierResult.isRight());

        return permissionFuture;
    }

    @Override
    public CompletableFuture canLookupAsync(TopicName topicName, String role, AuthenticationDataSource authenticationData) {
        if (!role.startsWith("biscuit:")) {
            return defaultProvider.canLookupAsync(topicName, role, authenticationData);
        }

        CompletableFuture permissionFuture = new CompletableFuture<>();

        Either res = verifierFromBiscuit(role);
        if (res.isLeft()) {
            permissionFuture.complete(false);
            return permissionFuture;
        }

        Verifier verifier = res.get();

        verifier.add_fact(fact("topic_operation",
                Arrays.asList(s("ambient"), string(topicName.getTenant()), string(topicName.getNamespacePortion()), string(topicName.getLocalName()), s("lookup"))));
        verifier.add_fact(fact("topic",
                Arrays.asList(s("ambient"), string(topicName.getTenant()), string(topicName.getNamespacePortion()), string(topicName.getLocalName()))));
        verifier.add_caveat(new Caveat(Arrays.asList(
                rule("check_right",
                        Arrays.asList(),
                        Arrays.asList(pred("right",
                                Arrays.asList(s("authority"), string(topicName.getTenant()), string(topicName.getNamespacePortion()), string(topicName.getLocalName()), s("lookup"))))
                ))));

        Either verifierResult = verifier.verify();
        if (verifierResult.isLeft()) {
            log.error("lookup verifier failure: {}", verifierResult.getLeft());
        } else {
            log.info("lookup authorized by biscuit token");
        }

        permissionFuture.complete(verifierResult.isRight());

        return permissionFuture;
    }

    @Override
    public CompletableFuture allowFunctionOpsAsync(NamespaceName namespaceName, String role, AuthenticationDataSource authenticationData) {
        if (!role.startsWith("biscuit:")) {
            return defaultProvider.allowFunctionOpsAsync(namespaceName, role, authenticationData);
        }

        CompletableFuture permissionFuture = new CompletableFuture<>();

        Either res = verifierFromBiscuit(role);
        if (res.isLeft()) {
            permissionFuture.complete(false);
            return permissionFuture;
        }

        Verifier verifier = res.get();

        verifier.add_fact(fact("namespace", Arrays.asList(s("ambient"), string(namespaceName.getTenant()), string(namespaceName.getLocalName()))));
        verifier.add_operation("functions");
        verifier.set_time();

        verifier.add_caveat(caveat(rule(
                "checked_allowfunction_right",
                Arrays.asList(string(namespaceName.getTenant()), string(namespaceName.getLocalName())),
                Arrays.asList(
                        pred("right", Arrays.asList(s("authority"), s("namespace"), string(namespaceName.getTenant()), string(namespaceName.getLocalName()), s("functions")))
                )
        )));

        Either verifierResult = verifier.verify();
        permissionFuture.complete(verifierResult.isRight());

        return permissionFuture;
    }

    @Override
    public CompletableFuture isSuperUser(String role, ServiceConfiguration serviceConfiguration) {
        if (!role.startsWith("biscuit:")) {
            return defaultProvider.isSuperUser(role, serviceConfiguration);
        }

        CompletableFuture permissionFuture = new CompletableFuture<>();

        Either res = verifierFromBiscuit(role);
        if (res.isLeft()) {
            permissionFuture.complete(false);
            return permissionFuture;
        }

        Verifier verifier = res.get();

        verifier.add_caveat(caveat(rule(
                "checked_issuperuser_right",
                Arrays.asList(s("admin")),
                Arrays.asList(pred("right", Arrays.asList(s("authority"), s("admin")))
                ))));

        //log.debug(verifier.print_world());
        Either verifierResult = verifier.verify();
        if (verifierResult.isLeft()) {
            log.error("verifier failure: {}", verifierResult.getLeft());
        } else {
            log.debug("superuser authorized by biscuit token");
        }

        permissionFuture.complete(verifierResult.isRight());

        return permissionFuture;
    }

    @Override
    public CompletableFuture isSuperUser(String role, AuthenticationDataSource authenticationData, ServiceConfiguration serviceConfiguration) {
        if (!role.startsWith("biscuit:")) {
            return defaultProvider.isSuperUser(role, serviceConfiguration);
        }

        CompletableFuture permissionFuture = new CompletableFuture<>();

        Either res = verifierFromBiscuit(role);
        if (res.isLeft()) {
            permissionFuture.complete(false);
            return permissionFuture;
        }

        Verifier verifier = res.get();

        verifier.add_caveat(caveat(rule(
                "checked_issuperuser_right",
                Arrays.asList(s("admin")),
                Arrays.asList(pred("right", Arrays.asList(s("authority"), s("admin")))
                ))));

        Either verifierResult = verifier.verify();
        if (verifierResult.isLeft()) {
            log.error("verifier failure: {}", verifierResult.getLeft());
        } else {
            log.debug("superuser authorized by biscuit token");
        }

        permissionFuture.complete(verifierResult.isRight());

        return permissionFuture;
    }

    @Override
    public CompletableFuture allowTenantOperationAsync(String tenantName, String originalRole, String role, TenantOperation operation, AuthenticationDataSource authData) {
        if (!role.startsWith("biscuit:")) {
            return defaultProvider.allowTenantOperationAsync(tenantName, originalRole, originalRole, operation, authData);
        }

        return isSuperUser(role, conf).thenCompose(isSuperUser -> {
            if (isSuperUser) {
                return CompletableFuture.completedFuture(true);
            } else {
                return FutureUtil.failedFuture(new IllegalStateException("allowTenantOperationAsync is not implemented for biscuit."));
            }
        });
    }

    @Override
    public CompletableFuture allowNamespaceOperationAsync(NamespaceName namespaceName, String originalRole, String role, NamespaceOperation operation, AuthenticationDataSource authData) {
        if (!role.startsWith("biscuit:")) {
            return defaultProvider.allowNamespaceOperationAsync(namespaceName, originalRole, originalRole, operation, authData);
        }

        log.debug(String.format("allowNamespaceOperationAsync [%s] on [%s]...", operation.toString(), namespaceName.toString()));
        CompletableFuture permissionFuture = new CompletableFuture<>();

        Either res = verifierFromBiscuit(role);
        if (res.isLeft()) {
            permissionFuture.complete(false);
            return permissionFuture;
        }

        Verifier verifier = res.get();

        Optional operationName = Stream.of(NamespaceOperation.values()).filter(e -> e == operation).findFirst();
        if (operationName.isPresent()) {
            // NamespaceOperation CREATE_TOPIC returns operation "create_topic"
            verifier.add_fact(fact("namespace_operation",
                    Arrays.asList(s("ambient"), string(namespaceName.getTenant()), string(namespaceName.getLocalName()), s(operationName.get().toString().toLowerCase()))));
            verifier.add_fact(fact("namespace",
                    Arrays.asList(s("ambient"), string(namespaceName.getTenant()), string(namespaceName.getLocalName()))));
            verifier.add_caveat(new Caveat(Arrays.asList(
                    rule("check_right",
                            Arrays.asList(),
                            Arrays.asList(pred("right",
                                    Arrays.asList(s("authority"),  string(namespaceName.getTenant()), string(namespaceName.getLocalName()), s(operationName.get().toString().toLowerCase()))))
                    ))));
        } else {
            throw new IllegalStateException(String.format("allowNamespacePolicyOperationAsync [%s] is not implemented.", operation.toString()));
        }

        //log.info(verifier.print_world());
        Either verifierResult = verifier.verify();
        if (verifierResult.isLeft()) {
            log.error("verifier failure: {}", verifierResult.getLeft());
        } else {
            log.debug(String.format("allowNamespaceOperationAsync [%s] on [%s] authorized", operation.toString(), namespaceName.toString()));
        }

        permissionFuture.complete(verifierResult.isRight());

        return permissionFuture.thenCompose(isAuthorized -> {
            if (isAuthorized) {
                return CompletableFuture.completedFuture(true);
            } else {
                return isSuperUser(role, conf);
            }
        });
    }

    @Override
    public CompletableFuture allowNamespacePolicyOperationAsync(NamespaceName namespaceName, PolicyName policy, PolicyOperation operation, String originalRole, String role, AuthenticationDataSource authData) {
        if (!role.startsWith("biscuit:")) {
            return defaultProvider.allowNamespacePolicyOperationAsync(namespaceName, policy, operation, originalRole, role, authData);
        }

        log.debug(String.format("allowNamespacePolicyOperationAsync [%s]:[%s] on [%s]...", policy.toString(), operation.toString(), namespaceName.toString()));
        CompletableFuture permissionFuture = new CompletableFuture<>();

        Either res = verifierFromBiscuit(role);
        if (res.isLeft()) {
            permissionFuture.complete(false);
            return permissionFuture;
        }

        Verifier verifier = res.get();
        Optional policyName = Stream.of(PolicyName.values()).filter(e -> e == policy).findFirst();

        if (policyName.isPresent()) {
            // PolicyName OFFLOAD, operation READ returns operation "offload_read"
            verifier.add_fact(fact("namespace_operation",
                    Arrays.asList(s("ambient"), string(namespaceName.getTenant()), string(namespaceName.getLocalName()), s(policyName.get().toString().toLowerCase() + "_" + operation.toString().toLowerCase()))));
            verifier.add_fact(fact("namespace",
                    Arrays.asList(s("ambient"), string(namespaceName.getTenant()), string(namespaceName.getLocalName()))));
            verifier.add_caveat(new Caveat(Arrays.asList(
                    rule("check_right",
                            Arrays.asList(),
                            Arrays.asList(pred("right",
                                    Arrays.asList(s("authority"),  string(namespaceName.getTenant()), string(namespaceName.getLocalName()), s(policyName.get().toString().toLowerCase() + "_" + operation.toString().toLowerCase()))))
                    ))));
        } else {
            throw new IllegalStateException(String.format("allowNamespacePolicyOperationAsync [%s] is not implemented.", operation.toString()));
        }

        //log.info(verifier.print_world());

        Either verifierResult = verifier.verify();
        if (verifierResult.isLeft()) {
            log.error("verifier failure: {}", verifierResult.getLeft());
        } else {
            log.debug(String.format("allowNamespacePolicyOperationAsync [%s]:[%s] on [%s] authorized.", policy.toString(), operation.toString(), namespaceName.toString()));
        }

        permissionFuture.complete(verifierResult.isRight());

        return permissionFuture.thenCompose(isAuthorized -> {
            if (isAuthorized) {
                return CompletableFuture.completedFuture(true);
            } else {
                return isSuperUser(role, conf);
            }
        });
    }

    @Override
    public CompletableFuture allowTopicOperationAsync(TopicName topicName, String originalRole, String role,
                                                               TopicOperation operation,
                                                               AuthenticationDataSource authData) {
        CompletableFuture isAuthorizedFuture;

        switch (operation) {
            case LOOKUP:
                isAuthorizedFuture = canLookupAsync(topicName, role, authData);
                break;
            case PRODUCE:
                isAuthorizedFuture = canProduceAsync(topicName, role, authData);
                break;
            case CONSUME:
                isAuthorizedFuture = canConsumeAsync(topicName, role, authData, authData.getSubscription());
                break;
            default:
                isAuthorizedFuture = FutureUtil.failedFuture(
                        new IllegalStateException("TopicOperation is not supported."));
        }

        return isAuthorizedFuture.thenCompose(isAuthorized -> {
            if (isAuthorized) {
                return CompletableFuture.completedFuture(true);
            } else {
                return isSuperUser(role, conf);
            }
        });
    }

    // those management functions will be performed outside of the authorization provider
    @Override
    public CompletableFuture grantPermissionAsync(NamespaceName namespace, Set actions, String role, String authDataJson) {
        return defaultProvider.grantPermissionAsync(namespace, actions, role, authDataJson);
    }

    @Override
    public CompletableFuture grantSubscriptionPermissionAsync(NamespaceName namespace, String subscriptionName, Set roles, String authDataJson) {
        return defaultProvider.grantSubscriptionPermissionAsync(namespace, subscriptionName, roles, authDataJson);
    }

    @Override
    public CompletableFuture revokeSubscriptionPermissionAsync(NamespaceName namespace, String subscriptionName, String role, String authDataJson) {
        return defaultProvider.revokeSubscriptionPermissionAsync(namespace, subscriptionName, role, authDataJson);
    }

    @Override
    public CompletableFuture grantPermissionAsync(TopicName topicName, Set actions, String role, String authDataJson) {
        return defaultProvider.grantPermissionAsync(topicName, actions, role, authDataJson);
    }

    @Override
    public void close() throws IOException {
        // noop
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy