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