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