Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add LDAP Group Provider Plugin #23900

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions core/trino-server/src/main/provisio/trino.xml
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,12 @@
</artifact>
</artifactSet>

<artifactSet to="plugin/ldap-group-provider">
<artifact id="${project.groupId}:trino-ldap-group-provider:zip:${project.version}">
<unpack />
</artifact>
</artifactSet>

<artifactSet to="plugin/mariadb">
<artifact id="${project.groupId}:trino-mariadb:zip:${project.version}">
<unpack />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Licensed 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 io.trino.plugin.base.group;

import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.inject.Inject;
import io.trino.cache.EvictableCacheBuilder;
import io.trino.plugin.base.group.CachingGroupProviderModule.ForCachingGroupProvider;
import io.trino.spi.security.GroupProvider;

import java.util.Set;

import static java.util.Objects.requireNonNull;
import static java.util.concurrent.TimeUnit.MILLISECONDS;

public class CachingGroupProvider
implements GroupProvider, GroupCacheInvalidationController
{
private final LoadingCache<String, Set<String>> cache;

@Inject
public CachingGroupProvider(CachingGroupProviderConfig config, @ForCachingGroupProvider GroupProvider delegate)
{
requireNonNull(delegate, "delegate is null");
this.cache = EvictableCacheBuilder.newBuilder()
.maximumSize(config.getCacheMaximumSize())
.expireAfterWrite(config.getTtl().toMillis(), MILLISECONDS)
.shareNothingWhenDisabled()
.build(CacheLoader.from(delegate::getGroups));
}

@Override
public Set<String> getGroups(String user)
{
return cache.getUnchecked(user);
}

@Override
public void invalidate(String user)
{
cache.invalidate(user);
}

@Override
public void invalidateAll()
{
cache.invalidateAll();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Licensed 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 io.trino.plugin.base.group;

import io.airlift.configuration.Config;
import io.airlift.configuration.ConfigDescription;
import io.airlift.units.Duration;
import jakarta.validation.constraints.Min;

import static java.util.Objects.requireNonNull;
import static java.util.concurrent.TimeUnit.SECONDS;

public class CachingGroupProviderConfig
{
private Duration ttl = new Duration(5, SECONDS);
private long cacheMaximumSize = Long.MAX_VALUE;

public Duration getTtl()
{
return ttl;
}

@Config("cache.ttl")
@ConfigDescription("Determines how long group information will be cached for each user")
public CachingGroupProviderConfig setTtl(Duration ttl)
{
this.ttl = requireNonNull(ttl, "ttl is null");
return this;
}

@Min(1)
public long getCacheMaximumSize()
{
return cacheMaximumSize;
}

@Config("cache.maximum-size")
@ConfigDescription("Maximum number of users for which groups are stored in the cache")
public CachingGroupProviderConfig setCacheMaximumSize(long cacheMaximumSize)
{
this.cacheMaximumSize = cacheMaximumSize;
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
/*
* Licensed 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 io.trino.plugin.base.group;

import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.inject.Binder;
import com.google.inject.BindingAnnotation;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.Scopes;
import io.airlift.configuration.AbstractConfigurationAwareModule;
import io.trino.spi.security.GroupProvider;

import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.util.Optional;

import static io.airlift.configuration.ConditionalModule.conditionalModule;
import static io.airlift.configuration.ConfigBinder.configBinder;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static java.util.Objects.requireNonNull;

/**
* If added to the list of {@link com.google.inject.Module}s used in initialization of a Guice context in a
* {@link io.trino.spi.security.GroupProviderFactory}, it will (almost) automatically add caching capability to the
* group provider. Requirements:
* <ul>
* <li>The {@link GroupProvider} available in the Guice context must be bound annotated with
* {@link ForCachingGroupProvider} binding annotation</li>
* </ul>
* The module will make the following configuration options available (to be set in {@code etc/group-provider.properties}:
* <ul>
* <li>{@code cache.enabled} - the toggle to enable or disable caching</li>
* <li>{@code cache.ttl} - determines how long group information will be cached for each user</li>
* <li>{@code cache.maximum-size} - maximum number of users for which groups are stored in the cache</li>
* </ul>
* These properties can optionally have an arbitrary prefix ({@link Builder#withPrefix(String)})
* and/or a binding annotation for the resulting binding of {@link GroupProvider} ({@link Builder#withBindingAnnotation(Class)}).
* <p>
* An additional object of type {@link GroupCacheInvalidationController} will also be bound, with which one can invalidate
* all or part of the cache.
*/
public class CachingGroupProviderModule
extends AbstractConfigurationAwareModule
{
private final Optional<String> prefix;
private final Optional<Class<? extends Annotation>> bindingAnnotation;

private CachingGroupProviderModule(Optional<String> prefix, Optional<Class<? extends Annotation>> bindingAnnotation)
{
this.prefix = requireNonNull(prefix, "prefix is null");
this.bindingAnnotation = requireNonNull(bindingAnnotation, "bindingAnnotation is null");
}

@Override
protected void setup(Binder binder)
{
configBinder(binder).bindConfig(GroupProviderConfig.class, prefix.orElse(null));
prefix.ifPresentOrElse(
prefix -> install(conditionalModule(
GroupProviderConfig.class,
prefix,
GroupProviderConfig::isCachingEnabled,
new CacheModule(Optional.of(prefix), bindingAnnotation),
new NonCacheModule(bindingAnnotation))),
() -> install(conditionalModule(
GroupProviderConfig.class,
GroupProviderConfig::isCachingEnabled,
new CacheModule(Optional.empty(), bindingAnnotation),
new NonCacheModule(bindingAnnotation))));
}

private static class CacheModule
implements Module
{
private final Optional<String> prefix;
private final Optional<Class<? extends Annotation>> bindingAnnotation;

public CacheModule(Optional<String> prefix, Optional<Class<? extends Annotation>> bindingAnnotation)
{
this.prefix = requireNonNull(prefix, "prefix is null");
this.bindingAnnotation = requireNonNull(bindingAnnotation, "bindingAnnotation is null");
}

@Override
public void configure(Binder binder)
{
configBinder(binder).bindConfig(CachingGroupProviderConfig.class, prefix.orElse(null));
binder.bind(CachingGroupProvider.class).in(Scopes.SINGLETON);
binder.bind(bindingAnnotation
.map(bindingAnnotation -> Key.get(GroupProvider.class, bindingAnnotation))
.orElseGet(() -> Key.get(GroupProvider.class)))
.to(CachingGroupProvider.class)
.in(Scopes.SINGLETON);
binder.bind(GroupCacheInvalidationController.class)
.to(CachingGroupProvider.class)
.in(Scopes.SINGLETON);
}
}

private static class NonCacheModule
implements Module
{
private final Optional<Class<? extends Annotation>> bindingAnnotation;

public NonCacheModule(Optional<Class<? extends Annotation>> bindingAnnotation)
{
this.bindingAnnotation = requireNonNull(bindingAnnotation, "bindingAnnotation is null");
}

@Override
public void configure(Binder binder)
{
binder.bind(bindingAnnotation
.map(bindingAnnotation -> Key.get(GroupProvider.class, bindingAnnotation))
.orElseGet(() -> Key.get(GroupProvider.class)))
.to(Key.get(GroupProvider.class, ForCachingGroupProvider.class))
.in(Scopes.SINGLETON);
binder.bind(GroupCacheInvalidationController.class)
.to(NoOpGroupCacheInvalidationController.class)
.in(Scopes.SINGLETON);
}
}

@Retention(RUNTIME)
@Target({FIELD, PARAMETER, METHOD})
@BindingAnnotation
public @interface ForCachingGroupProvider
{
}

public static CachingGroupProviderModule create()
{
return builder().build();
}

public static Builder builder()
{
return new Builder();
}

public static final class Builder
{
private Optional<String> prefix = Optional.empty();
private Optional<Class<? extends Annotation>> bindingAnnotation = Optional.empty();

private Builder() {}

@CanIgnoreReturnValue
public Builder withPrefix(String prefix)
{
this.prefix = Optional.of(prefix);
return this;
}

@CanIgnoreReturnValue
public Builder withBindingAnnotation(Class<? extends Annotation> bindingAnnotation)
{
this.bindingAnnotation = Optional.of(bindingAnnotation);
return this;
}

public CachingGroupProviderModule build()
{
return new CachingGroupProviderModule(prefix, bindingAnnotation);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Licensed 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 io.trino.plugin.base.group;

public interface GroupCacheInvalidationController
{
void invalidate(String user);

void invalidateAll();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Licensed 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 io.trino.plugin.base.group;

import io.airlift.configuration.Config;
import io.airlift.configuration.ConfigDescription;

public class GroupProviderConfig
{
private boolean isCachingEnabled;

public boolean isCachingEnabled()
{
return isCachingEnabled;
}

@Config("cache.enabled")
@ConfigDescription("Enables caching for the group provider")
public GroupProviderConfig setCachingEnabled(boolean isCachingEnabled)
{
this.isCachingEnabled = isCachingEnabled;
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Licensed 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 io.trino.plugin.base.group;

public class NoOpGroupCacheInvalidationController
implements GroupCacheInvalidationController
{
@Override
public void invalidate(String user) {}

@Override
public void invalidateAll() {}
}
Loading
Loading