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.cms.security;
35
36 import static info.magnolia.cms.security.SecurityConstants.NODE_GROUPS;
37 import static info.magnolia.cms.security.SecurityConstants.NODE_ROLES;
38 import info.magnolia.cms.core.Content;
39 import info.magnolia.cms.core.HierarchyManager;
40 import info.magnolia.cms.core.ItemType;
41 import info.magnolia.cms.core.MetaData;
42 import info.magnolia.cms.core.MgnlNodeType;
43 import info.magnolia.cms.core.Path;
44 import info.magnolia.cms.security.auth.ACL;
45 import info.magnolia.context.MgnlContext;
46 import info.magnolia.jcr.util.NodeUtil;
47 import info.magnolia.jcr.util.PropertyUtil;
48 import info.magnolia.repository.RepositoryConstants;
49
50 import java.util.ArrayList;
51 import java.util.Collection;
52 import java.util.Collections;
53 import java.util.GregorianCalendar;
54 import java.util.HashMap;
55 import java.util.HashSet;
56 import java.util.Iterator;
57 import java.util.List;
58 import java.util.Map;
59 import java.util.Set;
60 import java.util.SortedSet;
61 import java.util.TreeSet;
62
63 import javax.jcr.ItemNotFoundException;
64 import javax.jcr.Node;
65 import javax.jcr.NodeIterator;
66 import javax.jcr.PathNotFoundException;
67 import javax.jcr.Property;
68 import javax.jcr.PropertyIterator;
69 import javax.jcr.RepositoryException;
70 import javax.jcr.Session;
71 import javax.jcr.Value;
72 import javax.jcr.ValueFormatException;
73 import javax.jcr.query.Query;
74 import javax.security.auth.Subject;
75
76 import org.apache.commons.codec.binary.Base64;
77 import org.apache.commons.lang.StringUtils;
78 import org.slf4j.Logger;
79 import org.slf4j.LoggerFactory;
80
81
82
83
84
85
86 public class MgnlUserManager extends RepositoryBackedSecurityManager implements UserManager {
87
88 private static final Logger log = LoggerFactory.getLogger(MgnlUserManager.class);
89
90 public static final String PROPERTY_EMAIL = "email";
91 public static final String PROPERTY_LANGUAGE = "language";
92 public static final String PROPERTY_LASTACCESS = "lastaccess";
93 public static final String PROPERTY_PASSWORD = "pswd";
94 public static final String PROPERTY_TITLE = "title";
95 public static final String PROPERTY_ENABLED = "enabled";
96
97 public static final String NODE_ACLUSERS = "acl_users";
98
99 private String realmName;
100
101 private int maxFailedLoginAttempts;
102
103 private int lockTimePeriod;
104
105
106
107
108 public MgnlUserManager() {
109 }
110
111 @Override
112 public void setMaxFailedLoginAttempts(int maxFailedLoginAttempts){
113 this.maxFailedLoginAttempts = maxFailedLoginAttempts;
114 }
115
116 @Override
117 public int getMaxFailedLoginAttempts(){
118 return maxFailedLoginAttempts;
119 }
120
121 @Override
122 public int getLockTimePeriod() {
123 return lockTimePeriod;
124 }
125
126 @Override
127 public void setLockTimePeriod(int lockTimePeriod) {
128 this.lockTimePeriod = lockTimePeriod;
129 }
130
131 @Override
132 public User setProperty(final User user, final String propertyName, final Value propertyValue) {
133 MgnlContext.doInSystemContext(new SilentSessionOp<Void>(getRepositoryName()) {
134
135 @Override
136 public Void doExec(Session session) throws RepositoryException {
137 String path = ((MgnlUser) user).getPath();
138 try {
139 Node userNode = session.getNode(path);
140 if(propertyValue == null && PropertyUtil.getProperty(userNode, propertyName) != null){
141 userNode.getProperty(propertyName).remove();
142 }else if(propertyValue != null){
143 userNode.setProperty(propertyName, propertyValue);
144 }
145 session.save();
146 }
147 catch (RepositoryException e) {
148 session.refresh(false);
149 }
150 return null;
151 }
152 });
153 return user;
154 }
155
156
157
158
159
160 @Deprecated
161 public String getName() {
162 return getRealmName();
163 }
164
165
166
167
168 @Deprecated
169 public void setName(String name) {
170 setRealmName(name);
171 }
172
173 public void setRealmName(String name) {
174 this.realmName = name;
175 }
176
177 public String getRealmName() {
178 return realmName;
179 }
180
181
182
183
184
185
186 @Override
187 public User getUser(final String name) {
188 try {
189 return MgnlContext.doInSystemContext(new JCRSessionOp<User>(getRepositoryName()) {
190 @Override
191 public User exec(Session session) throws RepositoryException {
192 Node priviledgedUserNode = findPrincipalNode(name, session);
193 return newUserInstance(priviledgedUserNode);
194 }
195 @Override
196 public String toString() {
197 return "retrieve user " + name;
198 }
199 });
200 } catch (RepositoryException e) {
201 e.printStackTrace();
202 }
203 return null;
204 }
205
206 @Override
207 public User getUser(Subject subject) throws UnsupportedOperationException {
208
209 if (subject == null) {
210 log.debug("subject not set.");
211 return new DummyUser();
212 }
213
214 Set<User> principalSet = subject.getPrincipals(User.class);
215 Iterator<User> entityIterator = principalSet.iterator();
216 if (!entityIterator.hasNext()) {
217
218 log.debug("user name not contained in principal set.");
219 return new DummyUser();
220 }
221 return entityIterator.next();
222 }
223
224
225
226
227
228 @Deprecated
229 protected Content findUserNode(String realm, String name) throws RepositoryException {
230
231 throw new UnsupportedOperationException("Admin session is no longer kept open for unlimited duration of the time, therefore it is not possible to expose node outside of admin session.");
232 }
233
234
235
236
237 @Override
238 protected Node findPrincipalNode(String name, Session session) throws RepositoryException {
239 String realmName = getRealmName();
240 final String where;
241
242 if (Realm.REALM_ALL.getName().equals(realmName)) {
243 where = "where name() = '" + name + "'";
244 } else {
245
246 where = "where name() = '" + name + "' and isdescendantnode(['/" + realmName + "'])";
247
248
249 }
250
251 final String statement = "select * from [" + MgnlNodeType.USER + "] " + where;
252
253 Query query = session.getWorkspace().getQueryManager().createQuery(statement, Query.JCR_SQL2);
254 NodeIterator iter = query.execute().getNodes();
255 Node user = null;
256 while (iter.hasNext()) {
257 Node node = iter.nextNode();
258 if (node.isNodeType(ItemType.USER.getSystemName())) {
259 user = node;
260 break;
261 }
262 }
263 if (iter.hasNext()) {
264 log.error("More than one user found with name [{}] in realm [{}]");
265 }
266 return user;
267 }
268
269 protected User getFromRepository(String name) throws RepositoryException {
270 final Content node = findUserNode(this.realmName, name);
271 if (node == null) {
272 log.debug("User not found: [{}]", name);
273 return null;
274 }
275
276 return newUserInstance(node);
277 }
278
279
280
281
282 @Override
283 public User getSystemUser() throws UnsupportedOperationException {
284 throw new UnsupportedOperationException();
285 }
286
287
288
289
290 @Override
291 public User getAnonymousUser() throws UnsupportedOperationException {
292 throw new UnsupportedOperationException();
293 }
294
295
296
297
298 @Override
299 public Collection<User> getAllUsers() {
300 return MgnlContext.doInSystemContext(new SilentSessionOp<Collection<User>>(getRepositoryName()) {
301
302 @Override
303 public Collection<User> doExec(Session session) throws RepositoryException {
304 List<User> users = new ArrayList<User>();
305 Node node = session.getNode("/" + realmName);
306 updateUserListWithAllChildren(node, users);
307 return users;
308 }
309
310 @Override
311 public String toString() {
312 return "get all users";
313 }
314
315 });
316 }
317
318
319
320
321
322 public void updateUserListWithAllChildren(Node node, Collection<User> users) throws RepositoryException{
323 NodeIterator nodesIter = node.getNodes();
324 Collection<Node> nodes = new HashSet<Node>();
325 Collection<Node> folders = new HashSet<Node>();
326 while(nodesIter.hasNext()){
327 Node newNode = (Node) nodesIter.next();
328 if(newNode.isNodeType(MgnlNodeType.USER)){
329 nodes.add(newNode);
330 }else if(newNode.isNodeType(MgnlNodeType.NT_FOLDER)){
331 folders.add(newNode);
332 }
333 }
334
335 if(!nodes.isEmpty()){
336 for (Node userNode : nodes) {
337 users.add(newUserInstance(userNode));
338 }
339 }
340 if(!folders.isEmpty()){
341 Iterator<Node> it = folders.iterator();
342 while(it.hasNext()){
343 updateUserListWithAllChildren(it.next(), users);
344 }
345 }
346 }
347
348 @Override
349 public User createUser(final String name, final String pw) {
350 validateUsername(name);
351 return MgnlContext.doInSystemContext(new SilentSessionOp<MgnlUser>(getRepositoryName()) {
352
353 @Override
354 public MgnlUser doExec(Session session) throws RepositoryException {
355 Node userNode = session.getNode("/" + getRealmName()).addNode(name,ItemType.USER.getSystemName());
356 userNode.setProperty("name", name);
357 setPasswordProperty(userNode, pw);
358 userNode.setProperty("language", "en");
359
360 final String handle = userNode.getPath();
361 final Node acls = userNode.addNode(NODE_ACLUSERS, ItemType.CONTENTNODE.getSystemName());
362
363 Node acl = acls.addNode(Path.getUniqueLabel(session, acls.getPath(), "0"), ItemType.CONTENTNODE.getSystemName());
364 acl.setProperty("path", handle);
365 acl.setProperty("permissions", Permission.READ);
366
367 addWrite(handle, PROPERTY_EMAIL, acls);
368 addWrite(handle, PROPERTY_LANGUAGE, acls);
369 addWrite(handle, PROPERTY_LASTACCESS, acls);
370 addWrite(handle, PROPERTY_PASSWORD, acls);
371 addWrite(handle, PROPERTY_TITLE, acls);
372
373 addWrite(handle, MetaData.DEFAULT_META_NODE, acls);
374 session.save();
375 return new MgnlUser(userNode.getName(), getRealmName(), Collections.EMPTY_LIST, Collections.EMPTY_LIST, Collections.EMPTY_MAP, NodeUtil.getPathIfPossible(userNode), NodeUtil.getNodeIdentifierIfPossible(userNode));
376 }
377
378 @Override
379 public String toString() {
380 return "create user " + name;
381 }
382 });
383 }
384
385 @Override
386 public User changePassword(final User user, final String newPassword) {
387 return MgnlContext.doInSystemContext(new SilentSessionOp<User>(getRepositoryName()) {
388
389 @Override
390 public User doExec(Session session) throws RepositoryException {
391 Node userNode = session.getNode("/" + getRealmName() + "/" + user.getName());
392 setPasswordProperty(userNode, newPassword);
393
394 session.save();
395 return newUserInstance(userNode);
396 }
397
398 @Override
399 public String toString() {
400 return "change password of user " + user.getName();
401 }
402 });
403 }
404
405
406
407
408 @Deprecated
409 protected void setPasswordProperty(Content userNode, String clearPassword) throws RepositoryException {
410 setPasswordProperty(userNode.getJCRNode(), clearPassword);
411 }
412
413
414 protected void setPasswordProperty(Node userNode, String clearPassword) throws RepositoryException {
415 userNode.setProperty(PROPERTY_PASSWORD, encodePassword(clearPassword));
416 }
417
418 protected String encodePassword(String clearPassword) {
419 return new String(Base64.encodeBase64(clearPassword.getBytes()));
420 }
421
422 protected void validateUsername(String name) {
423 if (StringUtils.isBlank(name)) {
424 throw new IllegalArgumentException(name + " is not a valid username.");
425 }
426 }
427
428 protected Content createUserNode(String name) throws RepositoryException {
429 final String path = "/" + getRealmName();
430 return getHierarchyManager().createContent(path, name, ItemType.USER.getSystemName());
431 }
432
433
434
435
436 protected HierarchyManager getHierarchyManager() {
437 return MgnlContext.getSystemContext().getHierarchyManager(RepositoryConstants.USERS);
438 }
439
440
441
442
443 @Deprecated
444 protected MgnlUser userInstance(Content node) {
445 try {
446 return (MgnlUser) newUserInstance(node.getJCRNode());
447 } catch (RepositoryException e) {
448 log.error(e.getMessage(), e);
449 return null;
450 }
451 }
452
453
454
455
456
457
458 @Deprecated
459 protected User newUserInstance(Content node) {
460 try {
461 return newUserInstance(node.getJCRNode());
462 } catch (RepositoryException e) {
463 log.error(e.getMessage(), e);
464 return null;
465 }
466 }
467
468 private Node addWrite(String parentPath, String property, Node acls) throws PathNotFoundException, RepositoryException, AccessDeniedException {
469 Node acl = acls.addNode(Path.getUniqueLabel(acls.getSession(), acls.getPath(), "0"), ItemType.CONTENTNODE.getSystemName());
470 acl.setProperty("path", parentPath + "/" + property);
471 acl.setProperty("permissions", Permission.ALL);
472 return acl;
473 }
474
475 @Override
476 public void updateLastAccessTimestamp(final User user) throws UnsupportedOperationException {
477 MgnlContext.doInSystemContext(new SilentSessionOp<Void>(getRepositoryName()) {
478
479 @Override
480 public Void doExec(Session session) throws RepositoryException {
481 String path = ((MgnlUser) user).getPath();
482 log.debug("update access timestamp for {}", user.getName());
483 try {
484 Node userNode = session.getNode(path);
485 PropertyUtil.updateOrCreate(userNode, "lastaccess", new GregorianCalendar());
486 session.save();
487 }
488 catch (RepositoryException e) {
489 session.refresh(false);
490 }
491 return null;
492 }
493 @Override
494 public String toString() {
495 return "update user "+user.getName()+" last access time stamp";
496 }
497 });
498 }
499
500 protected User newUserInstance(Node privilegedUserNode) throws ValueFormatException, PathNotFoundException, RepositoryException {
501 if (privilegedUserNode == null) {
502 return null;
503 }
504 Set<String> roles = collectUniquePropertyNames(privilegedUserNode, "roles", RepositoryConstants.USER_ROLES, false);
505 Set<String> groups = collectUniquePropertyNames(privilegedUserNode, "groups", RepositoryConstants.USER_GROUPS, false);
506
507 Map<String, String> properties = new HashMap<String, String>();
508 for (PropertyIterator iter = privilegedUserNode.getProperties(); iter.hasNext(); ) {
509 Property prop = iter.nextProperty();
510 if (prop.getName().startsWith(MgnlNodeType.JCR_PREFIX) || prop.getName().startsWith(MgnlNodeType.MGNL_PREFIX)) {
511
512 continue;
513 }
514
515 properties.put(prop.getName(), prop.getString());
516 }
517
518 MgnlUser user = new MgnlUser(privilegedUserNode.getName(), getRealmName(), groups, roles, properties, NodeUtil.getPathIfPossible(privilegedUserNode), NodeUtil.getNodeIdentifierIfPossible(privilegedUserNode));
519 return user;
520 }
521
522 @Override
523 protected String getRepositoryName() {
524 return RepositoryConstants.USERS;
525 }
526
527
528
529
530 @Override
531 public Map<String, ACL> getACLs(final User user) {
532 if (!(user instanceof MgnlUser)) {
533 return null;
534 }
535 return super.getACLs(user.getName());
536 }
537
538 @Override
539 public User addRole(User user, String roleName) {
540 try {
541 super.add(user.getName(), roleName, NODE_ROLES);
542 } catch (PrincipalNotFoundException e) {
543
544 return null;
545 }
546 return getUser(user.getName());
547 }
548
549
550
551
552 private Set<String> collectUniquePropertyNames(Node rootNode, String subnodeName, String repositoryName, boolean isDeep) {
553 final SortedSet<String> set = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
554 String path = null;
555 try {
556 path = rootNode.getPath();
557 final Node node = rootNode.getNode(subnodeName);
558 collectUniquePropertyNames(node, repositoryName, subnodeName, set, isDeep);
559 collectUniquePropertyNames(rootNode.getNode(subnodeName), repositoryName, subnodeName, set, isDeep);
560 } catch (PathNotFoundException e) {
561 log.debug("{} does not have any {}", path, repositoryName);
562 } catch (Throwable t) {
563 log.error("Failed to read " + path + " or sub node " + subnodeName + " in repository " + repositoryName, t);
564 }
565 return set;
566 }
567
568 private void collectUniquePropertyNames(final Node node, final String repositoryName, final String subnodeName, final Collection<String> set, final boolean isDeep) throws RepositoryException {
569 MgnlContext.doInSystemContext(new JCRSessionOp<Void>(repositoryName) {
570
571 @Override
572 public Void exec(Session session) throws RepositoryException {
573 for (PropertyIterator props = node.getProperties(); props.hasNext();) {
574 Property property = props.nextProperty();
575 if (property.getName().startsWith(MgnlNodeType.JCR_PREFIX)) {
576 continue;
577 }
578 final String uuid = property.getString();
579 try {
580 final Node targetNode = session.getNodeByIdentifier(uuid);
581 set.add(targetNode.getName());
582 if (isDeep && targetNode.hasNode(subnodeName)) {
583 collectUniquePropertyNames(targetNode.getNode(subnodeName), repositoryName, subnodeName, set, true);
584 }
585 } catch (ItemNotFoundException t) {
586 final String path = property.getPath();
587
588 log.warn("Can't find {} node by UUID {} referred by node {}", new Object[]{repositoryName, t.getMessage(), path});
589 log.debug("Failed while reading node by UUID", t);
590
591
592 }
593 }
594 return null;
595 }
596 });
597 }
598
599 @Override
600 public User addGroup(User user, String groupName) {
601 try {
602 super.add(user.getName(), groupName, NODE_GROUPS);
603 } catch (PrincipalNotFoundException e) {
604
605 return null;
606 }
607 return getUser(user.getName());
608 }
609
610 @Override
611 public User removeGroup(User user, String groupName) {
612 super.remove(user.getName(), groupName, NODE_GROUPS);
613 return getUser(user.getName());
614 }
615
616 @Override
617 public User removeRole(User user, String roleName) {
618 super.remove(user.getName(), roleName, NODE_ROLES);
619 return getUser(user.getName());
620 }
621 }