1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.bremersee.acl.spring.data.mongodb;
18
19 import static org.springframework.core.annotation.AnnotationUtils.findAnnotation;
20 import static org.springframework.util.ObjectUtils.isEmpty;
21
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.List;
25 import java.util.Objects;
26 import java.util.Optional;
27 import java.util.Set;
28 import org.bremersee.acl.AccessEvaluation;
29 import org.bremersee.acl.Ace;
30 import org.bremersee.acl.Acl;
31 import org.bremersee.acl.AclUserContext;
32 import org.bremersee.acl.annotation.AclHolder;
33 import org.bremersee.acl.model.AccessControlEntryModifications;
34 import org.bremersee.acl.model.AccessControlListModifications;
35 import org.springframework.data.mongodb.core.query.Criteria;
36 import org.springframework.data.mongodb.core.query.Update;
37 import org.springframework.util.Assert;
38
39
40
41
42
43
44 public class AclCriteriaAndUpdateBuilder {
45
46 private final String aclPath;
47
48
49
50
51
52
53 public AclCriteriaAndUpdateBuilder(String aclPath) {
54 this.aclPath = Objects.isNull(aclPath) ? "" : aclPath;
55 }
56
57
58
59
60
61
62 public AclCriteriaAndUpdateBuilder(Class<?> entityClass) {
63 Assert.notNull(entityClass, "Entity class must be present.");
64 this.aclPath = Optional
65 .ofNullable(findAnnotation(entityClass, AclHolder.class))
66 .map(AclHolder::path)
67 .orElseThrow(() -> new IllegalArgumentException(String
68 .format(
69 "Entity class %s must be annotated with %s.",
70 entityClass.getSimpleName(), AclHolder.class.getSimpleName())));
71 }
72
73
74
75
76
77
78
79 public AclModificationUpdate buildUpdate(
80 AccessControlListModifications accessControlListModifications) {
81
82 Collection<AccessControlEntryModifications> mods = isEmpty(accessControlListModifications)
83 ? List.of()
84 : accessControlListModifications.getModificationsDistinct();
85
86 Update addAndSetUpdate = new Update();
87 Update removeUpdate = new Update();
88 boolean isSomethingRemoved = false;
89
90 for (AccessControlEntryModifications mod : mods) {
91
92
93 addAndSetUpdate = addAndSetUpdate.set(
94 path(Acl.ENTRIES, mod.getPermission(), Ace.GUEST),
95 mod.isGuest());
96
97
98 if (!mod.getAddUsers().isEmpty()) {
99 addAndSetUpdate = addAndSetUpdate
100 .addToSet(path(Acl.ENTRIES, mod.getPermission(), Ace.USERS))
101 .each((Object[]) mod.getAddUsers().toArray(new String[0]));
102 }
103 if (!mod.getRemoveUsers().isEmpty()) {
104 isSomethingRemoved = true;
105 removeUpdate = removeUpdate.pullAll(
106 path(Acl.ENTRIES, mod.getPermission(), Ace.USERS),
107 mod.getRemoveUsers().toArray(new String[0]));
108 }
109
110
111 if (!mod.getAddRoles().isEmpty()) {
112 addAndSetUpdate = addAndSetUpdate
113 .addToSet(path(Acl.ENTRIES, mod.getPermission(), Ace.ROLES))
114 .each((Object[]) mod.getAddRoles().toArray(new String[0]));
115 }
116 if (!mod.getRemoveRoles().isEmpty()) {
117 isSomethingRemoved = true;
118 removeUpdate = removeUpdate.pullAll(
119 path(Acl.ENTRIES, mod.getPermission(), Ace.ROLES),
120 mod.getRemoveRoles().toArray(new String[0]));
121 }
122
123 if (!mod.getAddGroups().isEmpty()) {
124 addAndSetUpdate = addAndSetUpdate
125 .addToSet(path(Acl.ENTRIES, mod.getPermission(), Ace.GROUPS))
126 .each((Object[]) mod.getAddGroups().toArray(new String[0]));
127 }
128 if (!mod.getRemoveGroups().isEmpty()) {
129 isSomethingRemoved = true;
130 removeUpdate = removeUpdate.pullAll(
131 path(Acl.ENTRIES, mod.getPermission(), Ace.GROUPS),
132 mod.getRemoveGroups().toArray(new String[0]));
133 }
134 }
135 return AclModificationUpdate.builder()
136 .preparationUpdates(isSomethingRemoved ? List.of(addAndSetUpdate) : List.of())
137 .finalUpdate(isSomethingRemoved ? removeUpdate : addAndSetUpdate)
138 .build();
139 }
140
141
142
143
144
145
146
147 public Update buildUpdate(Acl acl) {
148 return Update.update(path(), isEmpty(acl) ? Acl.builder().build() : acl);
149 }
150
151
152
153
154
155
156
157 public Update buildUpdate(String newOwner) {
158 return Update.update(path(Acl.OWNER), isEmpty(newOwner) ? "" : newOwner);
159 }
160
161
162
163
164
165
166
167 public Criteria buildUpdateOwnerCriteria(AclUserContext userContext) {
168 Assert.notNull(userContext, "User context must be present.");
169 return Criteria.where(path(Acl.OWNER)).is(userContext.getName());
170 }
171
172
173
174
175
176
177
178
179
180 public Criteria buildPermissionCriteria(
181 AclUserContext userContext,
182 AccessEvaluation accessEvaluation,
183 Collection<String> permissions) {
184
185 Assert.notNull(userContext, "User context must be present.");
186 Assert.notNull(accessEvaluation, "Access evaluation type must be present.");
187 Assert.notEmpty(permissions, "At least one permission must be present.");
188
189 List<Criteria> permissionCriteriaList = Set.copyOf(permissions).stream()
190 .map(permission -> createAccessCriteria(userContext, permission))
191 .toList();
192 Criteria permissionCriteria = accessEvaluation.isAnyPermission()
193 ? new Criteria().orOperator(permissionCriteriaList)
194 : new Criteria().andOperator(permissionCriteriaList);
195 if (userContext.getName().isBlank()) {
196 return permissionCriteria;
197 }
198 Criteria ownerCriteria = Criteria.where(path(Acl.OWNER)).is(userContext.getName());
199 return new Criteria().orOperator(ownerCriteria, permissionCriteria);
200 }
201
202 private Criteria createAccessCriteria(
203 AclUserContext userContext,
204 String permission) {
205
206 List<Criteria> criteriaList = new ArrayList<>();
207 criteriaList.add(Criteria.where(path(Acl.ENTRIES, permission, Ace.GUEST)).is(true));
208 if (!userContext.getName().isBlank()) {
209 criteriaList.add(Criteria
210 .where(path(Acl.ENTRIES, permission, Ace.USERS))
211 .all(userContext.getName()));
212 }
213 criteriaList.addAll(userContext.getRoles().stream()
214 .filter(role -> !isEmpty(role))
215 .map(role -> Criteria.
216 where(path(Acl.ENTRIES, permission, Ace.ROLES))
217 .all(role))
218 .toList()
219 );
220 criteriaList.addAll(userContext.getGroups().stream()
221 .filter(group -> !isEmpty(group))
222 .map(group -> Criteria
223 .where(path(Acl.ENTRIES, permission, Ace.GROUPS))
224 .all(group))
225 .toList()
226 );
227 return new Criteria().orOperator(criteriaList);
228 }
229
230 private String path(String... pathSegments) {
231 if (isEmpty(pathSegments)) {
232 return aclPath;
233 }
234 return aclPath + "." + String.join(".", pathSegments);
235 }
236
237 }