bug4811: a first potentially useful implementation in PermissionChangeListeners for the role add/remove use case

This commit is contained in:
Axel Uhl
2021-02-03 12:35:00 +01:00
parent 78d11af4ca
commit 842cf85b24
22 changed files with 292 additions and 57 deletions
@@ -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,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>
@@ -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;
@@ -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,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,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>
@@ -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,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,7 +1,6 @@
package com.sap.sse.security.shared;
@FunctionalInterface
public interface HasPermissionsProvider {
Iterable<HasPermissions> getAllHasPermissions();
}
@@ -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}
@@ -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)));
}
@@ -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.");
}
@@ -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() {