mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-09-21 05:05:31 +00:00
bug4811: a first potentially useful implementation in PermissionChangeListeners for the role add/remove use case
This commit is contained in:
+1
-1
@@ -113,7 +113,7 @@ import com.sap.sse.gwt.client.ErrorReporter;
|
||||
import com.sap.sse.gwt.client.ServerInfoDTO;
|
||||
import com.sap.sse.gwt.client.controls.filestorage.FileStoragePanel;
|
||||
import com.sap.sse.gwt.client.panels.HorizontalTabLayoutPanel;
|
||||
import com.sap.sse.landscape.common.SecuredLandscapeTypes;
|
||||
import com.sap.sse.landscape.common.shared.SecuredLandscapeTypes;
|
||||
import com.sap.sse.security.shared.HasPermissions.DefaultActions;
|
||||
import com.sap.sse.security.shared.impl.SecuredSecurityTypes;
|
||||
import com.sap.sse.security.shared.impl.SecuredSecurityTypes.ServerActions;
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 2.7.0//EN" "http://gwtproject.org/doctype/2.7.0/gwt-module.dtd">
|
||||
<module>
|
||||
<inherits name="com.sap.sse.landscape.SSELandscapeCommon"/>
|
||||
<inherits name="com.sap.sse.landscape.common.SSELandscapeCommon"/>
|
||||
<source path='common'/>
|
||||
</module>
|
||||
+1
-1
@@ -22,7 +22,7 @@ import com.sap.sse.gwt.client.celltable.TableWrapperWithSingleSelectionAndFilter
|
||||
import com.sap.sse.gwt.client.controls.busyindicator.BusyIndicator;
|
||||
import com.sap.sse.gwt.client.controls.busyindicator.SimpleBusyIndicator;
|
||||
import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback;
|
||||
import com.sap.sse.landscape.common.SecuredLandscapeTypes;
|
||||
import com.sap.sse.landscape.common.shared.SecuredLandscapeTypes;
|
||||
import com.sap.sse.security.shared.HasPermissions.DefaultActions;
|
||||
import com.sap.sse.security.ui.client.UserService;
|
||||
import com.sap.sse.security.ui.client.component.AccessControlledActionsColumn;
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ import com.sap.sse.gwt.server.ResultCachingProxiedRemoteServiceServlet;
|
||||
import com.sap.sse.landscape.application.ApplicationProcessMetrics;
|
||||
import com.sap.sse.landscape.aws.AwsLandscape;
|
||||
import com.sap.sse.landscape.aws.impl.AwsRegion;
|
||||
import com.sap.sse.landscape.common.SecuredLandscapeTypes;
|
||||
import com.sap.sse.landscape.common.shared.SecuredLandscapeTypes;
|
||||
import com.sap.sse.landscape.mongodb.MongoEndpoint;
|
||||
import com.sap.sse.landscape.mongodb.MongoProcess;
|
||||
import com.sap.sse.landscape.mongodb.MongoProcessInReplicaSet;
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
package com.sap.sailing.landscape.ui.shared;
|
||||
|
||||
import com.sap.sse.common.TimePoint;
|
||||
import com.sap.sse.landscape.common.SecuredLandscapeTypes;
|
||||
import com.sap.sse.landscape.common.shared.SecuredLandscapeTypes;
|
||||
import com.sap.sse.security.shared.HasPermissions;
|
||||
import com.sap.sse.security.shared.QualifiedObjectIdentifier;
|
||||
import com.sap.sse.security.shared.TypeRelativeObjectIdentifier;
|
||||
|
||||
@@ -225,8 +225,7 @@ public class Activator implements BundleActivator {
|
||||
SecurityUrlPathProviderSailingImpl.APPLICATION);
|
||||
context.registerService(SecurityUrlPathProvider.class, new SecurityUrlPathProviderSailingImpl(),
|
||||
sailingSecurityUrlPathProviderProperties);
|
||||
registrations.add(context.registerService(HasPermissionsProvider.class,
|
||||
(HasPermissionsProvider) SecuredDomainType::getAllInstances, null));
|
||||
registrations.add(context.registerService(HasPermissionsProvider.class, SecuredDomainType::getAllInstances, null));
|
||||
registrations.add(context.registerService(SecurityInitializationCustomizer.class,
|
||||
(SecurityInitializationCustomizer) securityService -> {
|
||||
final RoleDefinition sailingViewerRoleDefinition = securityService
|
||||
|
||||
@@ -312,6 +312,10 @@ public class Util {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the first element of the {@code iterable}, or {@code null} if the {@code iterable}
|
||||
* {@link #isEmpty(Iterable) is empty}.
|
||||
*/
|
||||
public static <T> T first(Iterable<T> iterable) {
|
||||
final Iterator<T> iter = iterable.iterator();
|
||||
final T result;
|
||||
|
||||
@@ -7,7 +7,9 @@ import org.osgi.framework.BundleContext;
|
||||
|
||||
import com.sap.sse.landscape.application.ApplicationProcessMetrics;
|
||||
import com.sap.sse.landscape.aws.AwsLandscape;
|
||||
import com.sap.sse.landscape.aws.SecuredAwsLandscapeType;
|
||||
import com.sap.sse.landscape.aws.persistence.PersistenceFactory;
|
||||
import com.sap.sse.security.shared.HasPermissionsProvider;
|
||||
|
||||
public class Activator implements BundleActivator {
|
||||
private final static Logger logger = Logger.getLogger(Activator.class.getName());
|
||||
@@ -31,6 +33,7 @@ public class Activator implements BundleActivator {
|
||||
PersistenceFactory.INSTANCE.getDefaultDomainObjectFactory(),
|
||||
PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory());
|
||||
}
|
||||
context.registerService(HasPermissionsProvider.class, SecuredAwsLandscapeType::getAllInstances, null);
|
||||
}
|
||||
|
||||
public static Activator getInstance() {
|
||||
|
||||
@@ -6,7 +6,9 @@ Bundle-Version: 1.0.0.qualifier
|
||||
Bundle-Vendor: SAP
|
||||
Automatic-Module-Name: com.sap.sse.landscape.common
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
|
||||
Export-Package: com.sap.sse.landscape.common
|
||||
Export-Package: com.sap.sse.landscape.common.shared
|
||||
Require-Bundle: com.sap.sse.security.common,
|
||||
com.sap.sse.common
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Import-Package: org.osgi.framework;version="1.8.0"
|
||||
Bundle-Activator: com.sap.sse.landscape.common.server.Activator
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 2.7.0//EN" "http://gwtproject.org/doctype/2.7.0/gwt-module.dtd">
|
||||
<module>
|
||||
<source path='common'/>
|
||||
<source path='shared'/>
|
||||
</module>
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package com.sap.sse.landscape.common.server;
|
||||
|
||||
import org.osgi.framework.BundleActivator;
|
||||
import org.osgi.framework.BundleContext;
|
||||
|
||||
import com.sap.sse.landscape.common.shared.SecuredLandscapeTypes;
|
||||
import com.sap.sse.security.shared.HasPermissionsProvider;
|
||||
|
||||
public class Activator implements BundleActivator {
|
||||
@Override
|
||||
public void start(BundleContext context) throws Exception {
|
||||
context.registerService(HasPermissionsProvider.class, SecuredLandscapeTypes::getAllInstances, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop(BundleContext context) throws Exception {
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.sap.sse.landscape.common;
|
||||
package com.sap.sse.landscape.common.shared;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
@@ -9,7 +9,7 @@ import com.sap.sse.common.Named;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
import com.sap.sse.common.impl.NamedImpl;
|
||||
import com.sap.sse.landscape.Host;
|
||||
import com.sap.sse.landscape.common.SecuredLandscapeTypes;
|
||||
import com.sap.sse.landscape.common.shared.SecuredLandscapeTypes;
|
||||
import com.sap.sse.security.shared.HasPermissions;
|
||||
import com.sap.sse.security.shared.QualifiedObjectIdentifier;
|
||||
import com.sap.sse.security.shared.TypeRelativeObjectIdentifier;
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package com.sap.sse.security.shared;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.sap.sse.common.Util;
|
||||
import com.sap.sse.security.shared.impl.WildcardPermissionEncoder;
|
||||
|
||||
/**
|
||||
@@ -26,6 +29,14 @@ public interface HasPermissions {
|
||||
* passed that is not contained in the array returned by this method.
|
||||
*/
|
||||
Action[] getAvailableActions();
|
||||
|
||||
/**
|
||||
* The {@link Action} from {@link #getAvailableActions()} whose {@link Action#name() name} equals that provided by parameter
|
||||
* {@code actionName}, or {@code null} if no such action exists
|
||||
*/
|
||||
default Action getActionByName(String actionName) {
|
||||
return Util.first(Util.filter(Arrays.asList(getAvailableActions()), action->action.name().equals(actionName)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code true} if and only if objects of this logical type support the {@code action} as one of their
|
||||
|
||||
+1
-2
@@ -1,7 +1,6 @@
|
||||
package com.sap.sse.security.shared;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface HasPermissionsProvider {
|
||||
|
||||
Iterable<HasPermissions> getAllHasPermissions();
|
||||
|
||||
}
|
||||
|
||||
+5
@@ -49,6 +49,11 @@ public interface QualifiedObjectIdentifier extends Serializable {
|
||||
*/
|
||||
WildcardPermission getPermission(Action action);
|
||||
|
||||
/**
|
||||
* Constructs a permission which describes the action specified by {@code actionName} on the object identified by this object.
|
||||
*/
|
||||
WildcardPermission getPermission(String actionName);
|
||||
|
||||
/**
|
||||
* @return the concatenation of {@link #getTypeIdentifier()}, {@link #QUALIFIER_SEPARATOR} and
|
||||
* {@link #getTypeRelativeObjectIdentifier()}'s {@link TypeRelativeObjectIdentifier#toString() toString}
|
||||
|
||||
+28
-13
@@ -175,7 +175,6 @@ public class WildcardPermission implements Serializable {
|
||||
private boolean implies(WildcardPermission wp, boolean checkWildcardPartsIfThisPermissionHasMoreParts) {
|
||||
// By default only supports comparisons with other WildcardPermissions
|
||||
List<Set<String>> otherParts = wp.getParts();
|
||||
|
||||
int i = 0;
|
||||
for (Set<String> otherPart : otherParts) {
|
||||
// If this permission has fewer parts than the other permission, everything after the number of parts contained
|
||||
@@ -190,7 +189,6 @@ public class WildcardPermission implements Serializable {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
if (checkWildcardPartsIfThisPermissionHasMoreParts) {
|
||||
// If this permission has more parts than the other parts, only imply it if all of the other parts are wildcards
|
||||
for (; i < getParts().size(); i++) {
|
||||
@@ -200,7 +198,6 @@ public class WildcardPermission implements Serializable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -246,8 +243,12 @@ public class WildcardPermission implements Serializable {
|
||||
|
||||
/**
|
||||
* For all combinations of first and third part produces a {@link QualifiedObjectIdentifier}. The result is never
|
||||
* {@code null} but may be empty. The third parts of this permission are
|
||||
* {@link PermissionStringEncoder#decodePermissionPart(String) decoded} before combined into the result objects.
|
||||
* {@code null} but may be empty, e.g., if no first part with type name(s) or no third part with object
|
||||
* identifier(s) exists. The third parts of this permission are
|
||||
* {@link PermissionStringEncoder#decodePermissionPart(String) decoded} before combined into the result objects. No
|
||||
* expansion happens should the first part or the third par contain a wildcard ("*"); instead, the wildcard
|
||||
* character will end up as the type name or type-relative object identifier, respectively, of all resulting
|
||||
* qualified object identifiers.
|
||||
*/
|
||||
public Iterable<QualifiedObjectIdentifier> getQualifiedObjectIdentifiers() {
|
||||
final List<QualifiedObjectIdentifier> result = new ArrayList<>();
|
||||
@@ -272,20 +273,34 @@ public class WildcardPermission implements Serializable {
|
||||
* or wildcard for the type part.
|
||||
*/
|
||||
public static class WildcardPermissionBuilder {
|
||||
private Set<String> types = new HashSet<>();
|
||||
private Set<String> actions = new HashSet<>();
|
||||
private Set<String> typeNames = new HashSet<>();
|
||||
private Set<String> actionNames = new HashSet<>();
|
||||
private Set<String> ids = new HashSet<>();
|
||||
|
||||
public WildcardPermissionBuilder withTypes(HasPermissions... types) {
|
||||
for (HasPermissions hasPermissions : types) {
|
||||
this.types.add(hasPermissions.getName());
|
||||
this.typeNames.add(hasPermissions.getName());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public WildcardPermissionBuilder withTypeNames(String... typeNames) {
|
||||
for (final String typeName : typeNames) {
|
||||
this.typeNames.add(typeName);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public WildcardPermissionBuilder withActions(Action... actions) {
|
||||
for (Action action : actions) {
|
||||
this.actions.add(action.name());
|
||||
this.actionNames.add(action.name());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public WildcardPermissionBuilder withActionNames(String... actionNames) {
|
||||
for (String actionName : actionNames) {
|
||||
this.actionNames.add(actionName);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -304,13 +319,13 @@ public class WildcardPermission implements Serializable {
|
||||
|
||||
public WildcardPermission build() {
|
||||
final List<Set<String>> parts = new ArrayList<>(2);
|
||||
if (types.isEmpty()) {
|
||||
if (typeNames.isEmpty()) {
|
||||
parts.add(new HashSet<>(Collections.singleton(WILDCARD_TOKEN)));
|
||||
} else {
|
||||
parts.add(types);
|
||||
parts.add(typeNames);
|
||||
}
|
||||
if (!actions.isEmpty()) {
|
||||
parts.add(actions);
|
||||
if (!actionNames.isEmpty()) {
|
||||
parts.add(actionNames);
|
||||
} else if (!ids.isEmpty()) {
|
||||
parts.add(new HashSet<>(Collections.singleton(WILDCARD_TOKEN)));
|
||||
}
|
||||
|
||||
+6
-1
@@ -51,7 +51,12 @@ public class QualifiedObjectIdentifierImpl implements QualifiedObjectIdentifier
|
||||
|
||||
@Override
|
||||
public WildcardPermission getPermission(Action action) {
|
||||
return new WildcardPermission(getTypeIdentifier()+WildcardPermission.PART_DIVIDER_TOKEN+action.name()+WildcardPermission.PART_DIVIDER_TOKEN+
|
||||
return getPermission(action.name());
|
||||
}
|
||||
|
||||
@Override
|
||||
public WildcardPermission getPermission(String actionName) {
|
||||
return new WildcardPermission(getTypeIdentifier()+WildcardPermission.PART_DIVIDER_TOKEN+actionName+WildcardPermission.PART_DIVIDER_TOKEN+
|
||||
getTypeRelativeObjectIdentifier());
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import org.apache.shiro.mgt.SecurityManager;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import org.osgi.framework.BundleContext;
|
||||
|
||||
import com.sap.sse.common.Util;
|
||||
import com.sap.sse.common.mail.MailException;
|
||||
import com.sap.sse.replication.ReplicableWithObjectInputStream;
|
||||
import com.sap.sse.security.impl.ReplicableSecurityService;
|
||||
@@ -27,6 +28,7 @@ import com.sap.sse.security.interfaces.UserStore;
|
||||
import com.sap.sse.security.operations.SecurityOperation;
|
||||
import com.sap.sse.security.shared.AccessControlListAnnotation;
|
||||
import com.sap.sse.security.shared.HasPermissions;
|
||||
import com.sap.sse.security.shared.HasPermissionsProvider;
|
||||
import com.sap.sse.security.shared.HasPermissions.DefaultActions;
|
||||
import com.sap.sse.security.shared.OwnershipAnnotation;
|
||||
import com.sap.sse.security.shared.PermissionChecker;
|
||||
@@ -698,4 +700,23 @@ public interface SecurityService extends ReplicableWithObjectInputStream<Replica
|
||||
* invoking the method has no effect.
|
||||
*/
|
||||
void removePermissionChangeListener(WildcardPermission permission, PermissionChangeListener listener);
|
||||
|
||||
/**
|
||||
* Obtains all {@link HasPermissions} secured types managed by this security service. In an OSGi environment, those
|
||||
* are obtained from all {@link HasPermissionsProvider}s registered as an OSGi service by any active bundle.
|
||||
*/
|
||||
Iterable<HasPermissions> getAllHasPermissions();
|
||||
|
||||
/**
|
||||
* Tries to find a {@link HasPermissions secured type} in the {@link #getAllHasPermissions() set of secured types
|
||||
* known by this security service} based on its name, like it is used in the first
|
||||
* {@link WildcardPermission#getParts() part} of a permission specification, as in {@code USER:READ:*}.
|
||||
*
|
||||
* @param securedTypeName
|
||||
* must not be {@code null}
|
||||
* @return may be {@code null} in case a secured type by that {@link HasPermissions#getName() name} is not found
|
||||
*/
|
||||
default HasPermissions getHasPermissionsByName(String securedTypeName) {
|
||||
return Util.first(Util.filter(getAllHasPermissions(), hp->hp.getName().equals(securedTypeName)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,12 +196,13 @@ public class Activator implements BundleActivator {
|
||||
new OSGIHasPermissionsProvider(hasPermissionsProviderTracker), sharedAcrossSubdomainsOf, baseUrlForCrossDomainStorage);
|
||||
initialSecurityService.initialize();
|
||||
securityService.complete(initialSecurityService);
|
||||
registration = context.registerService(SecurityService.class.getName(), initialSecurityService, null);
|
||||
registration = context.registerService(SecurityService.class, initialSecurityService, null);
|
||||
final Dictionary<String, String> replicableServiceProperties = new Hashtable<>();
|
||||
replicableServiceProperties.put(Replicable.OSGi_Service_Registry_ID_Property_Name,
|
||||
initialSecurityService.getId().toString());
|
||||
context.registerService(Replicable.class.getName(), initialSecurityService, replicableServiceProperties);
|
||||
context.registerService(ClearStateTestSupport.class.getName(), initialSecurityService, null);
|
||||
context.registerService(HasPermissionsProvider.class, SecuredSecurityTypes::getAllInstances, null);
|
||||
Logger.getLogger(Activator.class.getName()).info("Security Service registered.");
|
||||
}
|
||||
|
||||
|
||||
+175
-28
@@ -1,14 +1,22 @@
|
||||
package com.sap.sse.security.impl;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.sap.sse.common.Util;
|
||||
import com.sap.sse.concurrent.LockUtil;
|
||||
import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
|
||||
import com.sap.sse.security.PermissionChangeListener;
|
||||
import com.sap.sse.security.SecurityService;
|
||||
import com.sap.sse.security.shared.HasPermissions;
|
||||
import com.sap.sse.security.shared.HasPermissions.Action;
|
||||
import com.sap.sse.security.shared.OwnershipAnnotation;
|
||||
import com.sap.sse.security.shared.QualifiedObjectIdentifier;
|
||||
import com.sap.sse.security.shared.RoleDefinition;
|
||||
import com.sap.sse.security.shared.WildcardPermission;
|
||||
import com.sap.sse.security.shared.impl.Ownership;
|
||||
import com.sap.sse.security.shared.impl.Role;
|
||||
import com.sap.sse.security.shared.impl.User;
|
||||
import com.sap.sse.security.shared.impl.UserGroup;
|
||||
@@ -17,56 +25,195 @@ import com.sap.sse.security.shared.impl.UserGroup;
|
||||
* Keeps track of a set of {@link PermissionChangeListener}s, usually in the context of {@link SecurityServiceImpl}, to
|
||||
* efficiently derive possible triggers for notifying the listeners, based on, e.g., changes to object ownerships, user
|
||||
* group memberships, role and permission assignments to users, or changes to role definitions. The class is
|
||||
* thread-safe.
|
||||
* thread-safe.<p>
|
||||
*
|
||||
* The permissions managed in here are always single permissions regarding the single type, single action, and single
|
||||
* object ID to which they refer. If the {@link #addPermissionChangeListener(WildcardPermission, PermissionChangeListener)}
|
||||
* or {@link #removePermissionChangeListener(WildcardPermission, PermissionChangeListener)} methods receive a {@link WildcardPermission}
|
||||
* that does not have this property, it is expanded into the various single permissions, and the calls behave as if the listener
|
||||
* had been added / removed for each of those expanded single permissions.
|
||||
*
|
||||
* @author Axel Uhl (d043530)
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* @author Axel Uhl (D043530)
|
||||
*
|
||||
*/
|
||||
public class PermissionChangeListeners {
|
||||
private final NamedReentrantReadWriteLock lock;
|
||||
|
||||
// TODO the following data structure is not sufficient; we probably need the listeners grouped by
|
||||
// QualifiedObjectIdentifier, then action
|
||||
private final ConcurrentHashMap<WildcardPermission, ConcurrentHashMap<PermissionChangeListener, Boolean>> permissionChangeListeners;
|
||||
/**
|
||||
* Keeps track of the listeners, keyed by the {@link HasPermissions#getName() secured type name} which equals the
|
||||
* first {@link WildcardPermission#getParts() part} of the permissions that occur as key of the nested map. The
|
||||
* permissions used as keys of the nested map are single permissions, with exactly one type name, one action name,
|
||||
* and one object identifier.
|
||||
* <p>
|
||||
*
|
||||
* Modifications must be applied under the {@link NamedReentrantReadWriteLock#writeLock() write lock} of
|
||||
* {@link #lock}. Reading shall obtain the corresponding {@link NamedReentrantReadWriteLock#readLock() read lock}
|
||||
* accordingly.
|
||||
*/
|
||||
private final ConcurrentHashMap<String, ConcurrentHashMap<WildcardPermission, ConcurrentHashMap<PermissionChangeListener, Boolean>>> permissionChangeListenersByType;
|
||||
|
||||
/**
|
||||
* Keeps track of the listeners, keyed by the {@link QualifiedObjectIdentifier object identifier} to which the
|
||||
* permission applies and which is used in the third {@link WildcardPermission#getParts() part} of the permissions
|
||||
* used as keys in the nested map. The permissions used as keys of the nested map are single permissions, with
|
||||
* exactly one type name, one action name, and one object identifier.
|
||||
* <p>
|
||||
* Modifications must be applied under the {@link NamedReentrantReadWriteLock#writeLock() write lock} of
|
||||
* {@link #lock}. Reading shall obtain the corresponding {@link NamedReentrantReadWriteLock#readLock() read lock}
|
||||
* accordingly.
|
||||
*/
|
||||
private final ConcurrentHashMap<QualifiedObjectIdentifier, ConcurrentHashMap<WildcardPermission, ConcurrentHashMap<PermissionChangeListener, Boolean>>> permissionChangeListenersByObject;
|
||||
|
||||
private final SecurityService securityService;
|
||||
|
||||
public PermissionChangeListeners() {
|
||||
@FunctionalInterface
|
||||
private static interface ListenerAdderOrRemover {
|
||||
void addOrRemove(QualifiedObjectIdentifier oid, String action, PermissionChangeListener listener);
|
||||
}
|
||||
|
||||
public PermissionChangeListeners(final SecurityService securityService) {
|
||||
this.securityService = securityService;
|
||||
lock = new NamedReentrantReadWriteLock(PermissionChangeListeners.class.getSimpleName(), /* fair */ false);
|
||||
permissionChangeListeners = new ConcurrentHashMap<>();
|
||||
permissionChangeListenersByType = new ConcurrentHashMap<>();
|
||||
permissionChangeListenersByObject = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param permission
|
||||
* may contain multiple types and multiple object IDs; may contain a wildcard "*" for the action part. If
|
||||
* more than one type and/or more than one object ID and/or an action wildcard are provided, the method
|
||||
* behaves as if it had been invoked for each expanded single permission with a single type and a single
|
||||
* action and a single object ID. At least one type and object ID must be provided.
|
||||
*/
|
||||
public void addPermissionChangeListener(WildcardPermission permission, PermissionChangeListener listener) {
|
||||
for (final Set<String> partParts : permission.getParts()) {
|
||||
if (partParts.contains(WildcardPermission.WILDCARD_TOKEN)) {
|
||||
throw new IllegalArgumentException(
|
||||
"PermissionChangeListener can not be registered for wildcard permission " + permission
|
||||
+ ". Use specific type(s), operation(s), and object ID(s).");
|
||||
}
|
||||
}
|
||||
LockUtil.executeWithWriteLock(lock, () -> {
|
||||
permissionChangeListeners.computeIfAbsent(permission, p -> new ConcurrentHashMap<>()).put(listener, true);
|
||||
});
|
||||
addOrRemovePermissionChangeListener(permission, listener, this::addPermissionChangeListenerInternal);
|
||||
}
|
||||
|
||||
public void removePermissionChangeListener(WildcardPermission permission, PermissionChangeListener listener) {
|
||||
addOrRemovePermissionChangeListener(permission, listener, this::removePermissionChangeListenerInternal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands permission types, object IDs and actions into permissions with single event, single action, and single object ID and
|
||||
* then invokes {@code adderOrRemover} with the resulting single permissions. Type and object ID must not contain wildcards; actions may.
|
||||
*/
|
||||
private void addOrRemovePermissionChangeListener(WildcardPermission permission, PermissionChangeListener listener, ListenerAdderOrRemover adderOrRemover) {
|
||||
if (permission.getParts().size() < 3
|
||||
|| permission.getParts().get(0).contains(WildcardPermission.WILDCARD_TOKEN)
|
||||
|| permission.getParts().get(2).contains(WildcardPermission.WILDCARD_TOKEN)) {
|
||||
throw new IllegalArgumentException(
|
||||
"PermissionChangeListener can not be registered for wildcard permission " + permission
|
||||
+ ". Use at least specific type(s) and object ID(s).");
|
||||
}
|
||||
for (final QualifiedObjectIdentifier oid : permission.getQualifiedObjectIdentifiers()) {
|
||||
for (final String action : getActions(oid.getTypeIdentifier(), permission.getParts().get(1))) {
|
||||
adderOrRemover.addOrRemove(oid, action, listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addPermissionChangeListenerInternal(QualifiedObjectIdentifier oid, String actionName, PermissionChangeListener listener) {
|
||||
final WildcardPermission singlePermission = oid.getPermission(actionName);
|
||||
LockUtil.executeWithWriteLock(lock, () -> {
|
||||
final ConcurrentHashMap<PermissionChangeListener, Boolean> listenersForPermission = permissionChangeListeners
|
||||
.get(permission);
|
||||
if (listenersForPermission != null) {
|
||||
listenersForPermission.remove(listener);
|
||||
if (listenersForPermission.isEmpty()) {
|
||||
permissionChangeListeners.remove(permission);
|
||||
permissionChangeListenersByType.computeIfAbsent(oid.getTypeIdentifier(), typeName -> new ConcurrentHashMap<>())
|
||||
.computeIfAbsent(singlePermission, permission -> new ConcurrentHashMap<>())
|
||||
.put(listener, true);
|
||||
permissionChangeListenersByObject.computeIfAbsent(oid, p -> new ConcurrentHashMap<>())
|
||||
.computeIfAbsent(singlePermission, permission -> new ConcurrentHashMap<>())
|
||||
.put(listener, true);
|
||||
});
|
||||
}
|
||||
|
||||
private void removePermissionChangeListenerInternal(QualifiedObjectIdentifier oid, String actionName, PermissionChangeListener listener) {
|
||||
final WildcardPermission singlePermission = oid.getPermission(actionName);
|
||||
LockUtil.executeWithWriteLock(lock, () -> {
|
||||
final ConcurrentHashMap<WildcardPermission, ConcurrentHashMap<PermissionChangeListener, Boolean>> map =
|
||||
permissionChangeListenersByType.get(oid.getTypeIdentifier());
|
||||
if (map != null) {
|
||||
ConcurrentHashMap<PermissionChangeListener, Boolean> innerMap = map.get(singlePermission);
|
||||
if (innerMap != null) {
|
||||
innerMap.remove(listener);
|
||||
}
|
||||
}
|
||||
final ConcurrentHashMap<WildcardPermission, ConcurrentHashMap<PermissionChangeListener, Boolean>> map2 =
|
||||
permissionChangeListenersByObject.get(oid);
|
||||
if (map2 != null) {
|
||||
ConcurrentHashMap<PermissionChangeListener, Boolean> innerMap2 = map2.get(singlePermission);
|
||||
if (innerMap2 != null) {
|
||||
innerMap2.remove(listener);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Iterable<String> getActions(String securedTypeName, Set<String> actions) {
|
||||
final Set<String> result = new HashSet<>();
|
||||
final HasPermissions securedType = securityService.getHasPermissionsByName(securedTypeName);
|
||||
if (securedType != null) {
|
||||
for (final String actionString : actions) {
|
||||
if (actionString.equals(WildcardPermission.WILDCARD_TOKEN)) {
|
||||
for (final Action action : securedType.getAvailableActions()) {
|
||||
result.add(action.name());
|
||||
}
|
||||
} else {
|
||||
result.add(actionString);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyzes a change of permission/role on user: this happens on a specific {@code user}, and we can check if the
|
||||
* permission added/removed implies any permission for which a listener registered; this does still not necessarily
|
||||
* imply a change (the user could, e.g., have obtained the permission in more than one way), but this kind of change
|
||||
* will usually happen at very low frequency and in almost all cases will effectively cause a change. If the role is
|
||||
* qualified (user/group), check the qualification against the ownerships of the objects identified by the
|
||||
* permissions for which we have listener registrations.
|
||||
*/
|
||||
public void roleAddedToOrRemovedFromUser(User user, Role role) {
|
||||
// TODO change permission/role on user: this happens on a specific user, and we can if the permission
|
||||
// added/removed implies the permission in question; this does still not necessarily imply a change (the user
|
||||
// could, e.g., have obtained the permission in more than one way), but this kind of change will usually happen
|
||||
// at very low frequency and in almost all cases will effectively cause a change
|
||||
//
|
||||
// We need:
|
||||
LockUtil.executeWithReadLock(lock, () -> {
|
||||
outer: for (final WildcardPermission permission : role.getPermissions()) {
|
||||
// filter by type if provided; for type wildcard, all listener registrations will have to be scanned
|
||||
final Iterable<String> typesToScan;
|
||||
if (!permission.getParts().isEmpty()) {
|
||||
if (permission.getParts().get(0).contains(WildcardPermission.WILDCARD_TOKEN)) {
|
||||
typesToScan = permissionChangeListenersByType.keySet();
|
||||
} else {
|
||||
typesToScan = permission.getParts().get(0);
|
||||
}
|
||||
for (final String typeName : typesToScan) {
|
||||
for (final Entry<WildcardPermission, ConcurrentHashMap<PermissionChangeListener, Boolean>> permissionAndListener : permissionChangeListenersByType.get(typeName).entrySet()) {
|
||||
// all listener registrations' permissions have been expanded into single permissions upon registration:
|
||||
final QualifiedObjectIdentifier objectId = permissionAndListener.getKey().getQualifiedObjectIdentifiers().iterator().next();
|
||||
final OwnershipAnnotation ownershipAnnotation = securityService.getOwnership(objectId);
|
||||
if (ownershipAnnotation == null || matchesQualification(ownershipAnnotation.getAnnotation(), role.getQualifiedForTenant(), role.getQualifiedForUser())) {
|
||||
if (permission.implies(permissionAndListener.getKey())) {
|
||||
notifyListeners(permissionAndListener.getKey(), permissionAndListener.getValue().keySet());
|
||||
break outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void notifyListeners(WildcardPermission permission, Iterable<PermissionChangeListener> listeners) {
|
||||
final Iterable<User> usersWithPermissions = securityService.getUsersWithPermissions(permission);
|
||||
for (final PermissionChangeListener listener : listeners) {
|
||||
listener.setOfUsersWithPermissionChanged(permission, usersWithPermissions);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean matchesQualification(Ownership ownership, UserGroup qualifiedForTenant, User qualifiedForUser) {
|
||||
return (qualifiedForTenant == null || Util.equalsWithNull(qualifiedForTenant, ownership.getTenantOwner()))
|
||||
&& (qualifiedForUser == null || Util.equalsWithNull(qualifiedForUser, ownership.getUserOwner()));
|
||||
}
|
||||
|
||||
public void permissionAddedToOrRemovedFromUser(User user, WildcardPermission permission) {
|
||||
|
||||
@@ -296,7 +296,7 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat
|
||||
logger.info("Initializing Security Service with user store " + userStore);
|
||||
this.currentlyFillingFromInitialLoad = false;
|
||||
this.currentlyFillingFromInitialLoad = false;
|
||||
this.permissionChangeListeners = new PermissionChangeListeners();
|
||||
this.permissionChangeListeners = new PermissionChangeListeners(this);
|
||||
operationsSentToMasterForReplication = new HashSet<>();
|
||||
this.sharedAcrossSubdomainsOf = sharedAcrossSubdomainsOf;
|
||||
this.baseUrlForCrossDomainStorage = baseUrlForCrossDomainStorage;
|
||||
@@ -342,6 +342,11 @@ public class SecurityServiceImpl implements ReplicableSecurityService, ClearStat
|
||||
logger.info("Loaded "+count+" sessions");
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<HasPermissions> getAllHasPermissions() {
|
||||
return hasPermissionsProvider.getAllHasPermissions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCurrentlyFillingFromInitialLoad() {
|
||||
|
||||
Reference in New Issue
Block a user