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

Centralize plain Spring configuration for the GeoServer Plugin in gs-acl-client-plugin #73

Merged
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,14 @@ public void onAdminRuleEvent(AdminRuleEvent event) {
log.debug("evicted all {} admin authorizations upon event {}", evictCount, event);
}

public void evictAll() {
int dataAuth = evictAll(ruleAccessCache);
int adminAuth = evictAll(adminRuleAccessCache);
int summaries = evictAll(viewablesCache);
int total = dataAuth + adminAuth + summaries;
log.info("evicted {} cached ACL authorizations", total);
}

private int evictAll(Map<?, ?> cache) {
int size = cache.size();
cache.clear();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,94 @@
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;

import lombok.extern.slf4j.Slf4j;

import org.geoserver.acl.authorization.AccessInfo;
import org.geoserver.acl.authorization.AccessRequest;
import org.geoserver.acl.authorization.AccessSummary;
import org.geoserver.acl.authorization.AccessSummaryRequest;
import org.geoserver.acl.authorization.AdminAccessInfo;
import org.geoserver.acl.authorization.AdminAccessRequest;
import org.geoserver.acl.authorization.AuthorizationService;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.PropertyResolver;

import java.time.Duration;
import java.time.format.DateTimeParseException;
import java.util.Optional;
import java.util.concurrent.ConcurrentMap;

/**
* @since 2.0
*/
@Configuration
@EnableCaching
@Slf4j(topic = "org.geoserver.acl.authorization.cache")
public class CachingAuthorizationServiceConfiguration {

private static final Duration DEFAULT_CACHE_TTL = Duration.ofSeconds(30);

/**
* Defines the ACL Auth cache time-to-live from the {@code geoserver.acl.client.cache.ttl}
* config property, which only applies if there's no {@link CacheManager} in the application
* context or if it's not a {@link CaffeineCacheManager}.
*
* <p>The {@code geoserver.acl.client.cache.ttl} expects a {@link Duration} string, for example:
*
* <pre>
* <code>
* {@literal PT20.345S} -- parses as "20.345 seconds"
* {@literal PT15M} -- parses as "15 minutes" (where a minute is 60 seconds)
* {@literal PT10H} -- parses as "10 hours" (where an hour is 3600 seconds)
* {@literal P2D} -- parses as "2 days" (where a day is 24 hours or 86400 seconds)
* {@literal P2DT3H4M} -- parses as "2 days, 3 hours and 4 minutes"
* </code>
* </pre>
*
* Defaults to 30 seconds otherwise.
*/
@Bean
Duration aclAuthCacheTTL(
Optional<CacheManager> cacheManager, PropertyResolver propertyResolver) {

Duration expireAfterWrite = DEFAULT_CACHE_TTL;

if (caffeineCacheManager(cacheManager).isPresent()) {
log.info(
"CaffeineCacheManager is provided, ACL cache time-to-live won't be enforced. Define it in the cache configuration if necessary.");
} else {
final String ttl = propertyResolver.getProperty("geoserver.acl.client.cache.ttl");
if (ttl == null) {
log.info("Using default ACL cache time-to-live of {}", expireAfterWrite);
} else {
try {
expireAfterWrite = Duration.parse(ttl);
} catch (DateTimeParseException e) {
String msg =
String.format(
"Error parsing geoserver.acl.client.cache.ttl='%s', "
+ "expected a duration string (e.g. PT10S for 10 seconds)",
ttl);
throw new BeanInitializationException(msg);
}
if (expireAfterWrite.isNegative()) {
String msg =
String.format(
"Got Negative duration from geoserver.acl.client.cache.ttl='%s', "
+ "expected a positive duration string (e.g. PT10S for 10 seconds)",
ttl);
throw new BeanInitializationException(msg);
}
}
}
return expireAfterWrite;
}

@Bean
@Primary
CachingAuthorizationService cachingAuthorizationService(
Expand All @@ -43,38 +108,51 @@ CachingAuthorizationService cachingAuthorizationService(
}

@Bean
ConcurrentMap<AccessRequest, AccessInfo> aclAuthCache(CacheManager cacheManager) {
return getCache(cacheManager, "acl-data-grants");
ConcurrentMap<AccessRequest, AccessInfo> aclAuthCache(
Optional<CacheManager> cacheManager, @Qualifier("aclAuthCacheTTL") Duration ttl) {
return getCache(cacheManager, ttl, "acl-data-grants");
}

@Bean
ConcurrentMap<AdminAccessRequest, AdminAccessInfo> aclAdminAuthCache(
CacheManager cacheManager) {
return getCache(cacheManager, "acl-admin-grants");
Optional<CacheManager> cacheManager, @Qualifier("aclAuthCacheTTL") Duration ttl) {
return getCache(cacheManager, ttl, "acl-admin-grants");
}

@Bean
ConcurrentMap<AccessSummaryRequest, AccessSummary> aclViewablesCache(
CacheManager cacheManager) {
return getCache(cacheManager, "acl-access-summary");
Optional<CacheManager> cacheManager, @Qualifier("aclAuthCacheTTL") Duration ttl) {
return getCache(cacheManager, ttl, "acl-access-summary");
}

@SuppressWarnings("unchecked")
private <K, V> ConcurrentMap<K, V> getCache(CacheManager cacheManager, String cacheName) {
if (cacheManager instanceof CaffeineCacheManager) {
CaffeineCacheManager ccf = (CaffeineCacheManager) cacheManager;
org.springframework.cache.Cache cache = ccf.getCache(cacheName);
if (cache != null) {
Cache<K, V> caffeineCache = (Cache<K, V>) cache.getNativeCache();
return caffeineCache.asMap();
}
}
private <K, V> ConcurrentMap<K, V> getCache(
Optional<CacheManager> cacheManager, Duration ttl, String cacheName) {

return (ConcurrentMap<K, V>)
caffeineCacheManager(cacheManager)
.map(ccm -> getCache(ccm, cacheName))
.orElseGet(() -> newCache(ttl))
.asMap();
}

return newCache();
private Optional<CaffeineCacheManager> caffeineCacheManager(
Optional<CacheManager> cacheManager) {
return cacheManager
.filter(CaffeineCacheManager.class::isInstance)
.map(CaffeineCacheManager.class::cast);
}

@SuppressWarnings("unchecked")
private <K, V> Cache<K, V> getCache(CaffeineCacheManager ccm, String cacheName) {
org.springframework.cache.Cache cache = ccm.getCache(cacheName);
if (cache != null) {
return (Cache<K, V>) cache.getNativeCache();
}
return null;
}

private <K, V> ConcurrentMap<K, V> newCache() {
Cache<K, V> cache = Caffeine.newBuilder().softValues().build();
return cache.asMap();
private <K, V> Cache<K, V> newCache(Duration ttl) {
return Caffeine.newBuilder().softValues().expireAfterWrite(ttl).build();
}
}
19 changes: 0 additions & 19 deletions src/plugin/accessmanager/src/main/resources/applicationContext.xml

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
directory.
-->

<!-- load from AccessManagerSpringConfig -->
<context:component-scan base-package="org.geoserver.acl.plugin.config.accessmanager"/>

<!-- load from AuthorizationServiceTestConfig -->
<context:component-scan base-package="org.geoserver.acl.plugin.test.config"/>
Expand Down
19 changes: 0 additions & 19 deletions src/plugin/client/src/main/resources/applicationContext.xml

This file was deleted.

19 changes: 0 additions & 19 deletions src/plugin/config/src/main/resources/applicationContext.xml

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
*/
package org.geoserver.acl.plugin.autoconfigure.accessmanager;

import org.geoserver.acl.plugin.config.accessmanager.AccessManagerSpringConfig;
import org.geoserver.acl.plugin.config.configmanager.AclConfigurationManagerConfiguration;
import org.geoserver.acl.plugin.config.domain.client.ApiClientAclDomainServicesConfiguration;
import org.geoserver.acl.plugin.accessmanager.ACLResourceAccessManager;
import org.geoserver.acl.plugin.autoconfigure.conditionals.ConditionalOnAclEnabled;
import org.geoserver.acl.plugin.config.accessmanager.AclAccessManagerConfiguration;
import org.geoserver.security.ResourceAccessManager;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Import;

Expand All @@ -18,12 +19,9 @@
* delegating resource access requests to the GeoServer ACL service.
*
* @since 1.0
* @see AclAccessManagerConfiguration
*/
@AutoConfiguration
@ConditionalOnAclEnabled
@Import({ //
AclConfigurationManagerConfiguration.class, //
ApiClientAclDomainServicesConfiguration.class, //
AccessManagerSpringConfig.class
})
@Import(AclAccessManagerConfiguration.class)
public class AclAccessManagerAutoConfiguration {}
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,15 @@
*/
package org.geoserver.acl.plugin.autoconfigure.cache;

import lombok.extern.slf4j.Slf4j;

import org.geoserver.acl.authorization.cache.CachingAuthorizationServiceConfiguration;
import org.geoserver.acl.plugin.autoconfigure.accessmanager.AclAccessManagerAutoConfiguration;
import org.geoserver.acl.plugin.autoconfigure.accessmanager.ConditionalOnAclEnabled;
import org.geoserver.acl.plugin.autoconfigure.conditionals.ConditionalOnAclEnabled;
import org.geoserver.acl.plugin.config.cache.CachingAuthorizationServicePluginConfiguration;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Import;

import javax.annotation.PostConstruct;

/**
* @since 1.0
* @see CachingAuthorizationServiceConfiguration
Expand All @@ -25,12 +23,6 @@
name = "geoserver.acl.client.caching",
havingValue = "true",
matchIfMissing = true)
@Import(CachingAuthorizationServiceConfiguration.class)
@Slf4j(topic = "org.geoserver.acl.plugin.autoconfigure.cache")
public class CachingAuthorizationServicePluginAutoConfiguration {

@PostConstruct
void logUsing() {
log.info("Caching ACL AuthorizationService enabled");
}
}
@EnableCaching
@Import(CachingAuthorizationServicePluginConfiguration.class)
public class CachingAuthorizationServicePluginAutoConfiguration {}
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,26 @@
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.acl.plugin.autoconfigure.accessmanager;
package org.geoserver.acl.plugin.autoconfigure.conditionals;

import org.geoserver.acl.plugin.config.condition.AclEnabledCondition;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Spring Boot {@link AutoConfiguration @AutoConfiguration} conditional to enable/disable the plugin
*
* <p>For plain Spring (without spring boot auto configuration support), {@link
* AclEnabledCondition @Conditional(AclEnabledCondition.class)} is to be used on plain {@link
* Configuration @Configuration} classes
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/
package org.geoserver.acl.plugin.autoconfigure.webui;

import org.geoserver.acl.plugin.autoconfigure.accessmanager.ConditionalOnAclEnabled;
import org.geoserver.acl.plugin.autoconfigure.conditionals.ConditionalOnAclEnabled;
import org.geoserver.acl.plugin.config.webui.ACLWebUIConfiguration;
import org.geoserver.security.web.SecuritySettingsPage;
import org.geoserver.web.GeoServerBasePage;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/
package org.geoserver.acl.plugin.autoconfigure.wps;

import org.geoserver.acl.plugin.autoconfigure.accessmanager.ConditionalOnAclEnabled;
import org.geoserver.acl.plugin.autoconfigure.conditionals.ConditionalOnAclEnabled;
import org.geoserver.acl.plugin.config.wps.AclWpsIntegrationConfiguration;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/* (c) 2023 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.acl.plugin.config.accessmanager;

import org.geoserver.acl.plugin.accessmanager.ACLResourceAccessManager;
import org.geoserver.acl.plugin.config.configmanager.AclConfigurationManagerConfiguration;
import org.geoserver.acl.plugin.config.domain.client.ApiClientAclDomainServicesConfiguration;
import org.geoserver.security.ResourceAccessManager;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

/**
* {@link Configuration @Configuration} for the GeoServer Access Control List {@link
* ACLResourceAccessManager}.
*
* <p>{@link ACLResourceAccessManager} implements GeoServer {@link ResourceAccessManager} by
* delegating resource access requests to the GeoServer ACL service.
*
* @since 1.0
* @see AclConfigurationManagerConfiguration
* @see ApiClientAclDomainServicesConfiguration
* @see AccessManagerSpringConfig
*/
@Configuration
@Import({ //
AclConfigurationManagerConfiguration.class, //
ApiClientAclDomainServicesConfiguration.class, //
AccessManagerSpringConfig.class
})
public class AclAccessManagerConfiguration {}
Loading
Loading