Skip to content
Closed
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 @@ -62,7 +62,6 @@ default Stream<IAuthorizationAction> getActions( @Nullable String actionNamespac
.filter( action -> action.getName().startsWith( actionNamespacePrefix ) );
}

// TODO: tests
/**
* Gets an authorization action given its name.
* <p>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*! ******************************************************************************
*
* Pentaho
*
* Copyright (C) 2024 by Hitachi Vantara, LLC : http://www.pentaho.com
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file.
*
* Change Date: 2029-07-20
******************************************************************************/

package org.pentaho.platform.api.engine.security.authorization.caching;

import edu.umd.cs.findbugs.annotations.NonNull;
import org.pentaho.platform.api.engine.security.authorization.IAuthorizationOptions;
import org.pentaho.platform.api.engine.security.authorization.IAuthorizationRequest;
import org.pentaho.platform.api.engine.security.authorization.decisions.IAuthorizationDecision;

import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;

/**
* The {@code IAuthorizationDecisionCache} interface represents a cache for authorization decisions,
* and which follows the "loading cache" pattern.
*/
public interface IAuthorizationDecisionCache {
/**
* Gets a cached authorization decision for a specific authorization request and options, if available.
*
* @param request The authorization request.
* @param options The authorization options.
* @return An optional with the cached decision, if found; an empty optional, if not found.
*/
@NonNull
Optional<IAuthorizationDecision> get( @NonNull IAuthorizationRequest request,
@NonNull IAuthorizationOptions options );

/**
* Gets a cached authorization decision for a specific authorization request and options,
* loading it from the given loader function and storing it in the cache, if not available.
*
* @param request The authorization request.
* @param options The authorization options.
* @param loader A function that computes the authorization decision if it is not found in the cache.
* @return The authorization decision.
*/
@NonNull
IAuthorizationDecision get( @NonNull IAuthorizationRequest request,
@NonNull IAuthorizationOptions options,
@NonNull Function<IAuthorizationDecisionCacheKey, IAuthorizationDecision> loader );

/**
* Caches an authorization decision for a specific authorization request and options.
* <p>
* This operation is a no-op if the cache is disabled.
*
* @param request The authorization request.
* @param options The authorization options.
* @param decision The authorization decision to cache.
*/
void put( @NonNull IAuthorizationRequest request,
@NonNull IAuthorizationOptions options,
@NonNull IAuthorizationDecision decision );

/**
* Clears the cached authorization decision for a specific authorization request and options, if any.
*
* @param request The authorization request.
* @param options The authorization options.
*/
void invalidate( @NonNull IAuthorizationRequest request, @NonNull IAuthorizationOptions options );

/**
* Clears all cached authorization decisions for requests and options that match the given predicate.
*
* @param predicate A predicate that matches authorization requests and options to clear from the cache.
*/
void invalidateAll( @NonNull Predicate<IAuthorizationDecisionCacheKey> predicate );

/**
* Clears all cached authorization decisions.
* <p>
* This operation is a no-op if the cache is disabled.
*/
void invalidateAll();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*! ******************************************************************************
*
* Pentaho
*
* Copyright (C) 2024 by Hitachi Vantara, LLC : http://www.pentaho.com
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file.
*
* Change Date: 2029-07-20
******************************************************************************/

package org.pentaho.platform.api.engine.security.authorization.caching;

import edu.umd.cs.findbugs.annotations.NonNull;
import org.pentaho.platform.api.engine.security.authorization.IAuthorizationOptions;
import org.pentaho.platform.api.engine.security.authorization.IAuthorizationRequest;

/**
* The {@code IAuthorizationDecisionCacheKey} interface represents the key for cached authorization decision.
* It is constituted by an authorization request and options.
* <p>
* Implementations must implement proper {@code equals()} and {@code hashCode()} methods.
*/
public interface IAuthorizationDecisionCacheKey {
@NonNull
IAuthorizationRequest getRequest();

@NonNull
IAuthorizationOptions getOptions();
}
Original file line number Diff line number Diff line change
Expand Up @@ -326,13 +326,27 @@ not directly from the PentahoObjectFactory. -->

<bean id="authorizationActionService"
class="org.pentaho.platform.engine.security.authorization.PentahoSystemAuthorizationActionService">
<constructor-arg ref="IPluginManager" />
<pen:publish as-type="INTERFACES" />
</bean>

<bean id="authorizationDecisionCache"
class="org.pentaho.platform.engine.security.authorization.core.caching.MemoryAuthorizationDecisionCache">
<!-- expireAfterWrite, in seconds -->
<constructor-arg value="2" />
<!-- maximumSize -->
<constructor-arg value="1000" />
<!-- recordStats -->
<constructor-arg value="false" />

<pen:publish as-type="INTERFACES" />
</bean>

<bean id="authorizationService"
class="org.pentaho.platform.engine.security.authorization.core.AuthorizationService">
class="org.pentaho.platform.engine.security.authorization.core.CachingAuthorizationService">
<constructor-arg ref="authorizationActionService" />
<constructor-arg ref="rootAuthorizationRule" />
<constructor-arg ref="authorizationDecisionCache" />
<pen:publish as-type="INTERFACES" />
</bean>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*! ******************************************************************************
*
* Pentaho
*
* Copyright (C) 2024 by Hitachi Vantara, LLC : http://www.pentaho.com
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file.
*
* Change Date: 2029-07-20
******************************************************************************/

package org.pentaho.platform.engine.security.authorization;

import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import org.apache.commons.lang.StringUtils;
import org.pentaho.platform.api.engine.IAuthorizationAction;
import org.pentaho.platform.api.engine.IPentahoObjectReference;
import org.pentaho.platform.api.engine.security.authorization.IAuthorizationActionService;
import org.pentaho.platform.engine.core.system.objfac.spring.Const;
import org.springframework.util.Assert;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;

/**
* The {@code MemoryAuthorizationActionService} provides access to authorization actions stored in memory.
* The actions are received as Pentaho System object references, at construction.
*/
public class MemoryAuthorizationActionService implements IAuthorizationActionService {

@NonNull
private final List<IAuthorizationAction> actions;

@NonNull
private final Map<String, IAuthorizationAction> actionsByName;

@NonNull
private final List<IAuthorizationAction> systemActions;

@NonNull
private final List<IAuthorizationAction> pluginActions;

@NonNull
private final Map<String, List<IAuthorizationAction>> actionsByPlugin;

public MemoryAuthorizationActionService(
@NonNull Supplier<Stream<IPentahoObjectReference<IAuthorizationAction>>> authorizationActionsSupplier ) {

Assert.notNull( authorizationActionsSupplier, "Argument 'authorizationActionsSupplier' is required" );

actions = new ArrayList<>();
actionsByName = new HashMap<>();
systemActions = new ArrayList<>();
pluginActions = new ArrayList<>();
actionsByPlugin = new HashMap<>();

authorizationActionsSupplier
.get()
.filter( distinctByKey( IPentahoObjectReference::getObject ) )
.filter( ref -> ref.getObject() != null && StringUtils.isNotEmpty( ref.getObject().getName() ) )
.forEach( ref -> {
var action = ref.getObject();

actions.add( action );
actionsByName.put( action.getName(), action );

String pluginId = getPluginId( ref );
if ( StringUtils.isNotEmpty( pluginId ) ) {
pluginActions.add( action );
actionsByPlugin
.computeIfAbsent( pluginId, k -> new ArrayList<>() )
.add( action );
} else {
systemActions.add( action );
}
} );
}

/**
* Creates a predicate that filters out duplicate elements based on a key extracted from each element.
* <p>
* This method uses a concurrent set to track seen keys, ensuring that only the first occurrence of each key is kept.
* <p>
* Any elements with {@code null} keys are filtered out.
*
* @param keyExtractor A function to extract the key from each element.
* @param <T> The type of the elements in the stream.
* @return A predicate that returns true for the first occurrence of each key and false for duplicates.
*/
private static <T> Predicate<T> distinctByKey( Function<? super T, ?> keyExtractor ) {
Set<Object> seen = ConcurrentHashMap.newKeySet();
return t -> {
Object key = keyExtractor.apply( t );
return key != null && seen.add( key );
};
}

@Nullable
private static String getPluginId( @NonNull IPentahoObjectReference<IAuthorizationAction> actionReference ) {
return (String) actionReference.getAttributes().get( Const.PUBLISHER_PLUGIN_ID_ATTRIBUTE );
}

@NonNull
@Override
public Stream<IAuthorizationAction> getActions() {
return actions.stream();
}

@NonNull
@Override
public Optional<IAuthorizationAction> getAction( @NonNull String actionName ) {
Assert.hasLength( actionName, "Argument 'actionName' is required" );

return Optional.ofNullable( actionsByName.get( actionName ) );
}

@NonNull
@Override
public Stream<IAuthorizationAction> getSystemActions() {
return systemActions.stream();
}

@NonNull
@Override
public Stream<IAuthorizationAction> getPluginActions() {
return pluginActions.stream();
}

@NonNull
@Override
public Stream<IAuthorizationAction> getPluginActions( @NonNull String pluginId ) {
Assert.hasLength( pluginId, "Argument 'pluginId' is required" );

return actionsByPlugin.getOrDefault( pluginId, List.of() ).stream();
}
}
Loading