1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.security.app.dialog.field;
35
36 import static java.util.stream.Collectors.toMap;
37
38 import info.magnolia.cms.core.Path;
39 import info.magnolia.cms.security.PermissionImpl;
40 import info.magnolia.jcr.util.NodeTypes;
41
42 import java.util.Collection;
43 import java.util.Map;
44 import java.util.function.Function;
45
46 import javax.jcr.Node;
47 import javax.jcr.RepositoryException;
48
49 import org.apache.commons.lang3.StringUtils;
50
51
52
53
54
55
56
57
58
59 public class WorkspaceAccessControlList extends AccessControlList<WorkspaceAccessControlList.Entry> {
60
61 static final String ACCESS_TYPE_PROPERTY_NAME = "accessType";
62
63 public static final long ACCESS_TYPE_NODE = 1;
64 public static final long ACCESS_TYPE_CHILDREN = 2;
65 public static final long ACCESS_TYPE_NODE_AND_CHILDREN = ACCESS_TYPE_NODE | ACCESS_TYPE_CHILDREN;
66
67
68
69
70
71 @Override
72 protected Collection<Entry> createEntries(Node aclNode) throws RepositoryException {
73 Collection<Entry> rawEntries = super.createEntries(aclNode);
74 return mergeEntries(rawEntries);
75 }
76
77
78
79
80 @Override
81 protected Entry doCreateRawEntry(long permissions, String path) {
82
83 return new Entry(permissions, path);
84 }
85
86
87
88
89
90
91
92
93
94
95
96 @Override
97 public void saveEntries(Node aclNode) throws RepositoryException {
98
99 Collection<Entry> mergedEntries = mergeEntries(getEntries());
100
101 for (Entry entry : mergedEntries) {
102
103
104 if (StringUtils.isNotEmpty(entry.getPath())) {
105 Node entryNode = aclNode.addNode(Path.getUniqueLabel(aclNode, "0"), NodeTypes.ContentNode.NAME);
106
107 String path = entry.getPath();
108 long permissions = entry.getPermissions();
109 long accessType = entry.getAccessType();
110
111 String suffixForChildren = path.equals("/") ? "*" : "/*";
112 switch ((int) accessType) {
113 case (int) ACCESS_TYPE_CHILDREN:
114 path += suffixForChildren;
115 break;
116 case (int) ACCESS_TYPE_NODE_AND_CHILDREN:
117 String nodeName = Path.getUniqueLabel(aclNode, "0");
118 Node extraEntry = aclNode.addNode(nodeName, NodeTypes.ContentNode.NAME);
119 extraEntry.setProperty(PATH_PROPERTY_NAME, path + suffixForChildren);
120 extraEntry.setProperty(PERMISSIONS_PROPERTY_NAME, permissions);
121 break;
122 }
123
124 entryNode.setProperty(PERMISSIONS_PROPERTY_NAME, permissions);
125 entryNode.setProperty(PATH_PROPERTY_NAME, path);
126 }
127 }
128 }
129
130 Collection<Entry> mergeEntries(Collection<Entry> entries) {
131
132
133 Map<AccessControlList.Entry, Entry> mergedEntriesByKey = entries.stream()
134 .distinct()
135 .collect(toMap(Entry::getKey, Function.identity(), Entry::merge));
136
137
138 return mergedEntriesByKey.values();
139 }
140
141
142
143
144 public static class Entry extends AccessControlList.Entry {
145 private long accessType;
146
147
148
149
150 public Entry(long permissions, String path) {
151 super(permissions, path);
152 setPathAndAccessType(path);
153 }
154
155
156
157
158
159
160
161 public Entry(long permissions, long accessType, String path) throws IllegalArgumentException {
162 this(permissions, path);
163 if (accessType == 0) {
164 throw new IllegalArgumentException("Access type should be one of ACCESS_TYPE_NODE (1), ACCESS_TYPE_CHILDREN (2) or ACCESS_TYPE_NODE_AND_CHILDREN (3)");
165 }
166 this.accessType = accessType;
167 }
168
169
170
171
172 @Override
173 public void setPath(String path) {
174 setPathAndAccessType(path);
175 }
176
177
178
179
180
181
182
183 void setPathAndAccessType(String path) {
184
185 if(StringUtils.isEmpty(path)){
186 path = StringUtils.EMPTY;
187 }
188
189
190 path = deduplicateSlashesAndStars(path);
191 if (!path.equals("/") && path.endsWith("/")) {
192 path = StringUtils.removeEnd(path, "/");
193 }
194
195 if (path.endsWith("/*")) {
196
197 do {
198 path = StringUtils.removeEnd(path, "/*");
199 } while (path.endsWith("/*"));
200 if (path.isEmpty()) {
201 path = "/";
202 }
203 this.accessType |= ACCESS_TYPE_CHILDREN;
204 } else {
205
206 if (this.accessType == 0) {
207 this.accessType = ACCESS_TYPE_NODE;
208 }
209 }
210
211 super.setPath(path);
212 }
213
214 public long getAccessType() {
215 return accessType;
216 }
217
218 public void setAccessType(long accessType) {
219 this.accessType = accessType;
220 }
221
222
223
224
225 Entry.Key getKey() {
226 return new Entry.Key(getPermissions(), getPath());
227 }
228
229
230
231
232 Entry merge(Entry entry) {
233
234 if (!super.equals(entry)) {
235 throw new IllegalArgumentException("Can only merge ACL entries with same base path (without wildcard) and permission");
236 }
237 accessType |= entry.getAccessType();
238 return this;
239 }
240
241 private String deduplicateSlashesAndStars(String path) {
242 final StringBuilder builder = new StringBuilder();
243 char[] chars = path.toCharArray();
244 int i = 0;
245 char prevChar = 0;
246 while (i < chars.length) {
247 char c = chars[i];
248 if (i <= 0 || c != prevChar || (c != '*' && c != '/')) {
249 prevChar = c;
250 builder.append(c);
251 }
252 i++;
253 }
254 return builder.toString();
255 }
256
257 @Override
258 public boolean equals(Object o) {
259 if (this == o) return true;
260 if (o == null || getClass() != o.getClass()) return false;
261 if (!super.equals(o)) return false;
262
263 Entry entry = (Entry) o;
264 return accessType == entry.accessType;
265 }
266
267 @Override
268 public int hashCode() {
269 int result = super.hashCode();
270 result = 31 * result + (int) (accessType ^ (accessType >>> 32));
271 return result;
272 }
273
274 @Override
275 public String toString() {
276 return String.format("WorkspaceAccessControlList.Entry: %s\t%s\t\"%s\"", PermissionImpl.getPermissionAsName(getPermissions()), getAccessTypeName(accessType), getPath());
277 }
278
279
280
281
282
283 class Key extends AccessControlList.Entry {
284 Key(long permissions, String path) {
285 super(permissions, path);
286 }
287 }
288 }
289
290 private static String getAccessTypeName(long accessType) {
291 if (accessType == ACCESS_TYPE_NODE) {
292 return "Node";
293 } else if (accessType == ACCESS_TYPE_CHILDREN) {
294 return "Sub-nodes";
295 } else if (accessType == ACCESS_TYPE_NODE_AND_CHILDREN) {
296 return "Node and sub-nodes";
297 } else {
298 return String.format("Undefined (%d)", accessType);
299 }
300 }
301 }