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