Skip to content

Commit

Permalink
Restores dynamic types EMailMessage and Mailbox, adds transaction pre…
Browse files Browse the repository at this point in the history
…fetching hints, fixes performance of permission propagation queries, adds prefetching for user authentication.
  • Loading branch information
cmorgner committed Nov 6, 2024
1 parent 1fc925c commit 8bc6d8e
Show file tree
Hide file tree
Showing 67 changed files with 927 additions and 914 deletions.
101 changes: 47 additions & 54 deletions structr-base/src/main/java/org/structr/core/entity/AbstractNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -662,16 +662,6 @@ protected final <A extends NodeInterface, B extends NodeInterface, S extends Sou
return new IterableAdapter<>(getNode().getRelationships(direction, relType), factory);
}

/**
* Return statistical information on all relationships of this node
*
* @param dir
* @return number of relationships
*/
public final Map<String, Long> getRelationshipInfo(final Direction dir) throws FrameworkException {
return StructrApp.getInstance(securityContext).command(NodeRelationshipStatisticsCommand.class).execute(this, dir);
}

/**
* Returns the owner node of this node, following an INCOMING OWNS
* relationship.
Expand Down Expand Up @@ -777,12 +767,6 @@ private boolean isGranted(final Permission permission, final PrincipalInterface

if (doLog) { logger.info("{}{} ({}): {} check on level {} for {}", StringUtils.repeat(" ", level), getUuid(), getType(), permission.name(), level, accessingUser != null ? accessingUser.getName() : null); }

// use quick checks for maximum performance
if (isCreation && (accessingUser == null || accessingUser.equals(this) || accessingUser.equals(getOwnerNode()) ) ) {

return true;
}

if (accessingUser != null) {

// this includes SuperUser
Expand All @@ -798,15 +782,18 @@ private boolean isGranted(final Permission permission, final PrincipalInterface
}
}

final PrincipalInterface _owner = getOwnerNode();
final boolean hasOwner = (_owner != null);

if (isCreation && (accessingUser == null || accessingUser.equals(this) || accessingUser.equals(_owner) ) ) {
return true;
}

// allow accessingUser to access itself, but not parents etc.
if (this.equals(accessingUser) && (level == 0 || (permission.equals(Permission.read) && level > 0))) {
return true;
}

// check owner
final PrincipalInterface _owner = getOwnerNode();
final boolean hasOwner = (_owner != null);

// node has an owner, deny anonymous access
if (hasOwner && accessingUser == null) {
return false;
Expand All @@ -827,6 +814,13 @@ private boolean isGranted(final Permission permission, final PrincipalInterface
return true;
}

for (PrincipalInterface parent : accessingUser.getParentsPrivileged()) {

if (isGranted(permission, parent, mask, level+1, alreadyTraversed, false, doLog, localIncomingSecurityRelationships, isCreation)) {
return true;
}
}

// new experimental custom permission resultion based on query
final PropertyKey<String> permissionPropertyKey = StructrApp.getConfiguration().getPropertyKeyForJSONName(PrincipalInterface.class, "customPermissionQuery" + StringUtils.capitalize(permission.name()));
final String customPermissionQuery = accessingUser.getProperty(permissionPropertyKey);
Expand Down Expand Up @@ -907,14 +901,6 @@ private boolean isGranted(final Permission permission, final PrincipalInterface
// do backtracking
backtrack(root, accessingUser.getUuid(), permission, false, 0, doLog);
}

// Last: recursively check possible parent principals
for (PrincipalInterface parent : accessingUser.getParentsPrivileged()) {

if (isGranted(permission, parent, mask, level+1, alreadyTraversed, false, doLog, localIncomingSecurityRelationships, isCreation)) {
return true;
}
}
}

return false;
Expand Down Expand Up @@ -964,53 +950,60 @@ private boolean hasEffectivePermissions(final BFSInfo parent, final PrincipalInt

if (doLog) { logger.info("{}{} ({}): checking {} access on level {} for {}", StringUtils.repeat(" ", level), getUuid(), getType(), permission.name(), level, principal != null ? principal.getName() : null); }

for (final Class<Relation> propagatingType : SchemaRelationshipNode.getPropagatingRelationshipTypes()) {
final Node node = getNode();
final Map<String, Long> degree = node.getDegree();

for (final String type : degree.keySet()) {

// iterate over list of relationships
final List<Relation> list = Iterables.toList(getRelationshipsAsSuperUser(propagatingType));
final int count = list.size();
final int threshold = 1000;
final Class propagatingType = StructrApp.getConfiguration().getRelationshipEntityClass(type);

if (count < threshold) {
if (propagatingType != null && PermissionPropagation.class.isAssignableFrom(propagatingType)) {

for (final Relation source : list) {
// iterate over list of relationships
final List<Relation> list = Iterables.toList(getRelationshipsAsSuperUser(propagatingType));
final int count = list.size();
final int threshold = 1000;

if (source instanceof PermissionPropagation) {
if (count < threshold) {

final PermissionPropagation perm = (PermissionPropagation)source;
final RelationshipInterface rel = (RelationshipInterface)source;
for (final Relation source : list) {

if (doLog) { logger.info("{}{}: checking {} access on level {} via {} for {}", StringUtils.repeat(" ", level), getUuid(), permission.name(), level, rel.getRelType().name(), principal != null ? principal.getName() : null); }
if (source instanceof PermissionPropagation perm) {

// check propagation direction vs. evaluation direction
if (propagationAllowed(this, rel, perm.getPropagationDirection(), doLog)) {
if (doLog) {
logger.info("{}{}: checking {} access on level {} via {} for {}", StringUtils.repeat(" ", level), getUuid(), permission.name(), level, source.getRelType().name(), principal != null ? principal.getName() : null);
}

// check propagation direction vs. evaluation direction
if (propagationAllowed(this, source, perm.getPropagationDirection(), doLog)) {

applyCurrentStep(perm, mask);
applyCurrentStep(perm, mask);

if (mask.allowsPermission(permission)) {
if (mask.allowsPermission(permission)) {

final AbstractNode otherNode = (AbstractNode)rel.getOtherNode(this);
final AbstractNode otherNode = (AbstractNode) source.getOtherNode(this);

if (otherNode.isGranted(permission, principal, mask, level + 1, alreadyTraversed, false, doLog, isCreation)) {
if (otherNode.isGranted(permission, principal, mask, level + 1, alreadyTraversed, false, doLog, isCreation)) {

otherNode.storePermissionResolutionResult(principal.getUuid(), permission, true);
otherNode.storePermissionResolutionResult(principal.getUuid(), permission, true);

// break early
return true;
// break early
return true;

} else {
} else {

// add node to BFS queue
bfsNodes.add(new BFSInfo(parent, otherNode));
// add node to BFS queue
bfsNodes.add(new BFSInfo(parent, otherNode));
}
}
}
}
}
}

} else if (doLog) {
} else if (doLog) {

logger.warn("Refusing to resolve permissions with {} because there are more than {} nodes.", propagatingType.getSimpleName(), threshold);
logger.warn("Refusing to resolve permissions with {} because there are more than {} nodes.", propagatingType.getSimpleName(), threshold);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,7 @@

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.*;

public abstract class Principal extends AbstractNode implements PrincipalInterface, AccessControllable {

Expand Down Expand Up @@ -149,7 +146,7 @@ public Iterable<PrincipalInterface> getParentsPrivileged() {
final App app = StructrApp.getInstance();
final Principal privilegedPrincipal = app.get(Principal.class, getUuid());

return privilegedPrincipal.getProperty(StructrApp.key(Principal.class, "groups"));
return (Iterable)privilegedPrincipal.getProperty(Principal.groupsProperty);

} catch (FrameworkException fex) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
package org.structr.core.entity;

import org.apache.commons.collections4.SetUtils;
import org.structr.common.*;
import org.structr.common.error.FrameworkException;
import org.structr.core.entity.relationship.GroupCONTAINSPrincipal;
Expand All @@ -27,6 +28,8 @@
import org.structr.core.property.*;

import java.util.Date;
import java.util.LinkedHashSet;
import java.util.Set;

public interface PrincipalInterface extends NodeInterface, AccessControllable {

Expand Down Expand Up @@ -99,4 +102,24 @@ public interface PrincipalInterface extends NodeInterface, AccessControllable {
String getTwoFactorUrl();

default void onAuthenticate() {}


default Set<String> getOwnAndRecursiveParentsUuids() {

final Set<String> uuids = new LinkedHashSet<>();

recursiveCollectParentUuids(this, uuids);

return uuids;
}

default void recursiveCollectParentUuids(final PrincipalInterface principal, final Set<String> uuids) {

uuids.add(principal.getUuid());

for (final PrincipalInterface parent : principal.getParentsPrivileged()) {

recursiveCollectParentUuids(parent, uuids);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public static void flushAll() {
AccessPathCache.invalidate();
LocalizeFunction.invalidateCache();
AbstractSchemaNode.clearCachedSchemaMethods();
TransactionCommand.flushCaches();

StructrApp.getInstance().invalidateCache();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import org.structr.api.graph.Node;
import org.structr.api.graph.Relationship;
import org.structr.api.graph.RelationshipType;
import org.structr.common.RelType;
import org.structr.common.SecurityContext;
import org.structr.common.error.ErrorBuffer;
import org.structr.common.error.FrameworkException;
Expand Down Expand Up @@ -530,18 +529,16 @@ private void modifyEndNodes(final PrincipalInterface user, final NodeInterface s

final RelationshipType relType = rel.getRelType();

if (RelType.OWNS.equals(relType)) {
if ("OWNS".equals(relType.name())) {

modifyOwner(startNode);
modifyOwner(endNode);
return;
}

if (RelType.SECURITY.equals(relType)) {
if ("SECURITY".equals(relType.name())) {

modifySecurity(startNode);
modifySecurity(endNode);
return;
}

final Relation relation = Relation.getInstance((Class)rel.getClass());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,14 @@
import java.util.Map;
import java.util.Map.Entry;

//~--- classes ----------------------------------------------------------------

/**
* Returns an aggregated Map of relationship counts for the given node.
*
*
*/
public class NodeRelationshipStatisticsCommand extends NodeServiceCommand {

private static final Logger logger = LoggerFactory.getLogger(NodeRelationshipStatisticsCommand.class.getName());

//~--- methods --------------------------------------------------------

public Map<String, Long> execute(final AbstractNode sNode) throws FrameworkException {
return execute(sNode, null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,11 @@ public static void queuePostProcessProcedure(final Runnable runnable) {
}
}

public static void flushCaches() {
final DatabaseService graphDb = Services.getInstance().getDatabaseService();
graphDb.flushCaches();
}

private static void assertSameTransaction(final GraphObject obj, final long currentTransactionId) {

final long nodeTransactionId = obj.getSourceTransactionId();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ public void end() {
referenceCount--;
}

public int level() {
return referenceCount;
}

// ----- interface Transaction -----
@Override
public void failure() {
Expand Down Expand Up @@ -99,6 +103,11 @@ public Relationship getRelationship(final Identity id) {
return tx.getRelationship(id);
}

@Override
public void prefetchHint(final String hint) {
tx.prefetchHint(hint);
}

@Override
public void prefetch(final String type1, String type2, final Set<String> keys) {
tx.prefetch(type1, type2, keys);
Expand Down
10 changes: 10 additions & 0 deletions structr-base/src/main/java/org/structr/core/graph/Tx.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public class Tx implements AutoCloseable, Prefetcher {
private boolean doValidation = true;
private boolean doCallbacks = true;
private boolean doNotifications = true;
private String prefetchHint = null;

public Tx(final SecurityContext securityContext) {
this(securityContext, true, true);
Expand Down Expand Up @@ -70,6 +71,12 @@ public void success() throws FrameworkException {
TransactionCommand.commitTx(securityContext, doValidation);
}

@Override
public void prefetchHint(final String hint) {
TransactionCommand.getCurrentTransaction().prefetchHint(hint);
this.prefetchHint = hint;
}

@Override
public void prefetch(final String type1, final String type2, final Set<String> keys) {
TransactionCommand.getCurrentTransaction().prefetch(type1, type2, keys);
Expand Down Expand Up @@ -112,6 +119,9 @@ public void close() throws FrameworkException {
// experimental
try (final Tx tx = begin()) {

// copy prefetch hint to callback transaction
tx.prefetchHint(this.prefetchHint);

if (doCallbacks && modificationQueue != null) {

modificationQueue.doOuterCallbacks(securityContext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@
*/
package org.structr.core.graph.search;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.structr.api.Predicate;
import org.structr.api.Transaction;
import org.structr.api.graph.Direction;
import org.structr.api.graph.PropertyContainer;
import org.structr.api.index.Index;
Expand Down Expand Up @@ -58,9 +60,6 @@ public abstract class SearchCommand<S extends PropertyContainer, T extends Graph

private static final Logger logger = LoggerFactory.getLogger(SearchCommand.class.getName());

protected static final boolean INCLUDE_DELETED_AND_HIDDEN = true;
protected static final boolean PUBLIC_ONLY = false;

private static final Set<PropertyKey> indexedWarningDisabled = new LinkedHashSet<>(Arrays.asList(SchemaMethod.source, SchemaProperty.readFunction, SchemaProperty.writeFunction));
private static final Map<String, Set<String>> subtypeMapForType = new LinkedHashMap<>();
private static final Set<String> baseTypes = new LinkedHashSet<>();
Expand Down Expand Up @@ -107,9 +106,9 @@ private ResultStream<T> doSearch(final String description) throws FrameworkExcep
}
}

final Factory<S, T> factory = getFactory(securityContext, includeHidden, publicOnly, pageSize, page);
final PrincipalInterface user = securityContext.getUser(false);
final SearchConfig config = new SearchConfig();
final Factory<S, T> factory = getFactory(securityContext, includeHidden, publicOnly, pageSize, page);
final PrincipalInterface user = securityContext.getUser(false);
final SearchConfig config = new SearchConfig();

if (user == null) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,8 @@ public void run() {

try (final Tx tx = StructrApp.getInstance(securityContext).tx(true, true, false)) {

tx.prefetchHint("DirectoryWatchService main loop");

// handle all watch events that are older than 2 seconds
for (final Iterator<WatchEventItem> it = eventQueue.values().iterator(); it.hasNext();) {

Expand Down
Loading

0 comments on commit 8bc6d8e

Please sign in to comment.