Skip to content

Commit 5074b57

Browse files
committed
Ensure consistency of startsWith predicates about Realms (#1489)
1 parent 16fe2ed commit 5074b57

20 files changed

Lines changed: 62 additions & 55 deletions

File tree

‎core/idm/logic/src/main/java/org/apache/syncope/core/logic/ConnectorLogic.java‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ public ConnectorLogic(
8080
this.binder = binder;
8181
}
8282

83-
protected void securityChecks(final Set<String> effectiveRealms, final String realm, final String key) {
84-
if (effectiveRealms.stream().noneMatch(realm::startsWith)) {
83+
protected void securityChecks(final Set<String> realms, final String realm, final String key) {
84+
if (!RealmUtils.SubtreePredicate.of(realms).test(realm)) {
8585
throw new DelegatedAdministrationException(realm, ConnInstance.class.getSimpleName(), key);
8686
}
8787
}

‎core/idm/logic/src/main/java/org/apache/syncope/core/logic/ResourceLogic.java‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,8 @@ public ResourceLogic(
107107
this.anyUtilsFactory = anyUtilsFactory;
108108
}
109109

110-
protected void securityChecks(final Set<String> effectiveRealms, final String realm, final String key) {
111-
boolean authorized = effectiveRealms.stream().anyMatch(realm::startsWith);
112-
if (!authorized) {
110+
protected void securityChecks(final Set<String> realms, final String realm, final String key) {
111+
if (!RealmUtils.SubtreePredicate.of(realms).test(realm)) {
113112
throw new DelegatedAdministrationException(realm, ExternalResource.class.getSimpleName(), key);
114113
}
115114
}

‎core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/RealmLogic.java‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import org.apache.syncope.core.persistence.api.entity.Realm;
5151
import org.apache.syncope.core.persistence.api.entity.user.User;
5252
import org.apache.syncope.core.persistence.api.search.SyncopePage;
53+
import org.apache.syncope.core.persistence.api.utils.RealmUtils;
5354
import org.apache.syncope.core.provisioning.api.PropagationByResource;
5455
import org.apache.syncope.core.provisioning.api.data.RealmDataBinder;
5556
import org.apache.syncope.core.provisioning.api.propagation.PropagationManager;
@@ -110,9 +111,8 @@ public RealmLogic(
110111
this.taskExecutor = taskExecutor;
111112
}
112113

113-
protected void securityChecks(final Set<String> effectiveRealms, final String realm) {
114-
boolean authorized = effectiveRealms.stream().anyMatch(realm::startsWith);
115-
if (!authorized) {
114+
protected void securityChecks(final Set<String> realms, final String realm) {
115+
if (!RealmUtils.SubtreePredicate.of(realms).test(realm)) {
116116
throw new DelegatedAdministrationException(
117117
realm, User.class.getSimpleName(), AuthContextUtils.getUsername());
118118
}
@@ -143,7 +143,7 @@ public Page<RealmTO> search(
143143
getOrDefault(IdRepoEntitlement.REALM_SEARCH, Set.of());
144144
List<RealmTO> result = realmSearchDAO.search(baseRealms, effectiveCond, pageable).stream().
145145
map(realm -> binder.getRealmTO(
146-
realm, authorizations.stream().anyMatch(auth -> realm.getFullPath().startsWith(auth)))).
146+
realm, RealmUtils.SubtreePredicate.of(authorizations).test(realm.getFullPath()))).
147147
sorted(Comparator.comparing(RealmTO::getFullPath)).
148148
toList();
149149

‎core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/TaskLogic.java‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
import org.apache.syncope.core.persistence.api.entity.task.TaskUtilsFactory;
6666
import org.apache.syncope.core.persistence.api.search.SyncopePage;
6767
import org.apache.syncope.core.persistence.api.utils.ExceptionUtils2;
68+
import org.apache.syncope.core.persistence.api.utils.RealmUtils;
6869
import org.apache.syncope.core.provisioning.api.data.TaskDataBinder;
6970
import org.apache.syncope.core.provisioning.api.job.JobManager;
7071
import org.apache.syncope.core.provisioning.api.job.JobNamer;
@@ -127,8 +128,8 @@ public TaskLogic(
127128
}
128129

129130
protected void securityChecks(final String entitlement, final String realm) {
130-
Set<String> authRealms = AuthContextUtils.getAuthorizations().getOrDefault(entitlement, Set.of());
131-
if (authRealms.isEmpty() || authRealms.stream().noneMatch(realm::startsWith)) {
131+
Set<String> realms = AuthContextUtils.getAuthorizations().getOrDefault(entitlement, Set.of());
132+
if (!RealmUtils.SubtreePredicate.of(realms).test(realm)) {
132133
throw new DelegatedAdministrationException(realm, MacroTask.class.getSimpleName(), null);
133134
}
134135
}

‎core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/utils/RealmUtils.java‎

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@
2828
import java.util.Optional;
2929
import java.util.Set;
3030
import java.util.function.Predicate;
31-
import java.util.stream.Collectors;
3231
import org.apache.commons.lang3.ClassUtils;
32+
import org.apache.commons.lang3.Strings;
3333
import org.apache.syncope.common.lib.SyncopeConstants;
3434
import org.apache.syncope.core.persistence.api.entity.EntityFactory;
3535

@@ -53,9 +53,9 @@ public static boolean normalizingAddTo(final Set<String> realms, final String ne
5353
boolean dontAdd = false;
5454
Set<String> toRemove = new HashSet<>();
5555
for (String realm : realms) {
56-
if (newRealm.startsWith(realm)) {
56+
if (subtree(newRealm, realm)) {
5757
dontAdd = true;
58-
} else if (realm.startsWith(newRealm)) {
58+
} else if (subtree(realm, newRealm)) {
5959
toRemove.add(realm);
6060
}
6161
}
@@ -86,40 +86,43 @@ public static NormalizedRealms of(final Collection<String> input) {
8686
}
8787
}
8888

89-
private static class StartsWithPredicate implements Predicate<String> {
89+
public static final class SubtreePredicate implements Predicate<String> {
9090

91-
private final Collection<String> targets;
91+
public static SubtreePredicate of(final Collection<String> targets) {
92+
return new SubtreePredicate(targets);
93+
}
94+
95+
private final Collection<String> candidates;
9296

93-
StartsWithPredicate(final Collection<String> targets) {
94-
this.targets = targets;
97+
private SubtreePredicate(final Collection<String> candidates) {
98+
this.candidates = candidates;
9599
}
96100

97101
@Override
98102
public boolean test(final String realm) {
99-
return targets.stream().anyMatch(realm::startsWith);
103+
return candidates.stream().anyMatch(candidate -> subtree(realm, candidate));
100104
}
101105
}
102106

103-
private static final Predicate<String> DYN_REALMS_PREDICATE = r -> !r.startsWith(SyncopeConstants.ROOT_REALM);
107+
public static boolean subtree(final String realm, final String prefix) {
108+
return realm.equals(prefix) || realm.startsWith(Strings.CS.appendIfMissing(prefix, "/"));
109+
}
104110

105111
public static Set<String> getEffective(final Set<String> allowedRealms, final String requestedRealm) {
106112
NormalizedRealms normalized = NormalizedRealms.of(allowedRealms);
107113

108114
Set<String> requested = Set.of(requestedRealm);
109115

110-
StartsWithPredicate normalizedFilter = new StartsWithPredicate(normalized.realms());
111-
StartsWithPredicate requestedFilter = new StartsWithPredicate(requested);
112-
113116
Set<String> effective = new HashSet<>();
114-
effective.addAll(requested.stream().filter(normalizedFilter).collect(Collectors.toSet()));
115-
effective.addAll(normalized.realms().stream().filter(requestedFilter).collect(Collectors.toSet()));
117+
effective.addAll(requested.stream().filter(SubtreePredicate.of(normalized.realms())).toList());
118+
effective.addAll(normalized.realms().stream().filter(SubtreePredicate.of(requested)).toList());
116119

117120
// includes group ownership
118121
effective.addAll(normalized.groupOwnerRealms());
119122

120123
// includes dynamic realms
121124
if (allowedRealms != null) {
122-
effective.addAll(allowedRealms.stream().filter(DYN_REALMS_PREDICATE).collect(Collectors.toSet()));
125+
effective.addAll(allowedRealms.stream().filter(r -> !r.startsWith(SyncopeConstants.ROOT_REALM)).toList());
123126
}
124127

125128
return effective;

‎core/persistence-api/src/test/java/org/apache/syncope/core/persistence/api/utils/RealmUtilsTest.java‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,16 +47,16 @@ public void parseGroupOwnerRealm() {
4747
@Test
4848
public void normalizingAddTo() {
4949
Set<String> realms = new HashSet<>();
50-
realms.add("realm1");
51-
realms.add("realm2");
52-
String newRealm = "realm123";
50+
realms.add("/realm1");
51+
realms.add("/realm2");
52+
String newRealm = "/realm1/23";
5353
assertFalse(RealmUtils.normalizingAddTo(realms, newRealm));
5454
assertEquals(2, realms.size());
5555

5656
realms.clear();
57-
realms.add("testRealm1");
58-
realms.add("realm2");
59-
newRealm = "test";
57+
realms.add("/test/realm1");
58+
realms.add("/realm2");
59+
newRealm = "/test";
6060
assertTrue(RealmUtils.normalizingAddTo(realms, newRealm));
6161
assertEquals(2, realms.size());
6262
}

‎core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractJPAAnySearchDAO.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -722,7 +722,7 @@ protected AdminRealmsFilter getAdminRealmsFilter(
722722
realmKeys.clear();
723723
}
724724
} else {
725-
if (adminRealms.stream().anyMatch(r -> r.startsWith(base.getFullPath()))) {
725+
if (RealmUtils.SubtreePredicate.of(adminRealms).test(base.getFullPath())) {
726726
realmKeys.add(base.getKey());
727727
}
728728
}

‎core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/AnyObjectRepoExtImpl.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ public void securityChecks(
131131

132132
// 3. check if anyObject is in Realm (or descendants) for which AuthContextUtils.getUsername() owns entitlement
133133
if (!authorized) {
134-
authorized = authRealms.stream().anyMatch(realm::startsWith);
134+
authorized = RealmUtils.SubtreePredicate.of(authRealms).test(realm);
135135
}
136136

137137
if (!authorized) {

‎core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/ConnInstanceRepoExtImpl.java‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,12 @@
2626
import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
2727
import org.apache.syncope.core.persistence.api.entity.ConnInstance;
2828
import org.apache.syncope.core.persistence.api.entity.ExternalResource;
29+
import org.apache.syncope.core.persistence.api.utils.RealmUtils;
2930
import org.apache.syncope.core.persistence.jpa.entity.JPAConnInstance;
3031
import org.apache.syncope.core.spring.security.AuthContextUtils;
3132
import org.apache.syncope.core.spring.security.DelegatedAdministrationException;
3233
import org.springframework.transaction.annotation.Transactional;
34+
import org.springframework.util.CollectionUtils;
3335

3436
public class ConnInstanceRepoExtImpl implements ConnInstanceRepoExt {
3537

@@ -51,9 +53,8 @@ public ConnInstance authFind(final String key) {
5153
}
5254

5355
Set<String> authRealms = AuthContextUtils.getAuthorizations().get(IdMEntitlement.CONNECTOR_READ);
54-
if (authRealms == null || authRealms.isEmpty()
55-
|| authRealms.stream().noneMatch(
56-
realm -> connInstance.getAdminRealm().getFullPath().startsWith(realm))) {
56+
if (CollectionUtils.isEmpty(authRealms)
57+
|| !RealmUtils.SubtreePredicate.of(authRealms).test(connInstance.getAdminRealm().getFullPath())) {
5758

5859
throw new DelegatedAdministrationException(
5960
connInstance.getAdminRealm().getFullPath(),
@@ -75,7 +76,7 @@ public List<? extends ConnInstance> findAll() {
7576
"SELECT e FROM " + JPAConnInstance.class.getSimpleName() + " e", ConnInstance.class);
7677

7778
return query.getResultList().stream().filter(connInstance -> authRealms.stream().
78-
anyMatch(realm -> connInstance.getAdminRealm().getFullPath().startsWith(realm))).
79+
anyMatch(realm -> RealmUtils.subtree(connInstance.getAdminRealm().getFullPath(), realm))).
7980
toList();
8081
}
8182

‎core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/ExternalResourceRepoExtImpl.java‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.apache.syncope.core.persistence.api.entity.policy.Policy;
3939
import org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
4040
import org.apache.syncope.core.persistence.api.entity.policy.PushPolicy;
41+
import org.apache.syncope.core.persistence.api.utils.RealmUtils;
4142
import org.apache.syncope.core.persistence.jpa.entity.JPAExternalResource;
4243
import org.apache.syncope.core.spring.security.AuthContextUtils;
4344
import org.apache.syncope.core.spring.security.DelegatedAdministrationException;
@@ -84,7 +85,7 @@ public ExternalResource authFind(final String key) {
8485
Set<String> authRealms = AuthContextUtils.getAuthorizations().get(IdMEntitlement.RESOURCE_READ);
8586
if (authRealms == null || authRealms.isEmpty()
8687
|| authRealms.stream().noneMatch(realm -> resource.getConnector() != null
87-
&& resource.getConnector().getAdminRealm().getFullPath().startsWith(realm))) {
88+
&& RealmUtils.subtree(resource.getConnector().getAdminRealm().getFullPath(), realm))) {
8889

8990
throw new DelegatedAdministrationException(
9091
resource.getConnector().getAdminRealm().getFullPath(),
@@ -132,7 +133,7 @@ public List<? extends ExternalResource> findAll() {
132133

133134
return query.getResultList().stream().filter(resource -> authRealms.stream().
134135
anyMatch(realm -> resource.getConnector() != null
135-
&& resource.getConnector().getAdminRealm().getFullPath().startsWith(realm))).
136+
&& RealmUtils.subtree(resource.getConnector().getAdminRealm().getFullPath(), realm))).
136137
toList();
137138
}
138139

0 commit comments

Comments
 (0)