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