/
YarepUserManager.java
575 lines (513 loc) · 23.4 KB
/
YarepUserManager.java
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
package org.wyona.security.impl.yarep;
import java.util.HashMap;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.wyona.security.core.api.AccessManagementException;
import org.wyona.security.core.api.Group;
import org.wyona.security.core.api.IdentityManager;
import org.wyona.security.core.api.User;
import org.wyona.security.core.api.UserManager;
import org.wyona.yarep.core.NoSuchNodeException;
import org.wyona.yarep.core.Node;
import org.wyona.yarep.core.NodeType;
import org.wyona.yarep.core.Repository;
import org.wyona.yarep.core.RepositoryException;
/**
* The YarepUserManager expects to find all existing users under the node /users.
* If the node /users does not exist, it will look under the root node.
* All files which have <user> as root element will be recognized as a user
* configuration. <identity> is also recognized as a user for backwards
* compatibility.
*/
public class YarepUserManager implements UserManager {
protected static Logger log = LogManager.getLogger(YarepUserManager.class);
private Repository identitiesRepository;
protected IdentityManager identityManager;
private boolean cacheEnabled = false;
private HashMap cachedUsers;
private boolean resolveGroupsAtCreation = false;
private String SUFFIX = "xml";
private String DEPRECATED_SUFFIX = "iml";
private static final String PSEUDONYM = "pseudonym";
/**
* Constructor.
*
* @param identityManager
* @param identitiesRepository
* @param cacheEnabled Flag to enable memory cache
* @param resolveGroupsAtCreation Flag to resolve groups if user is created and maybe existed before
*
* @throws AccessManagementException
*/
public YarepUserManager(IdentityManager identityManager, Repository identitiesRepository, boolean cacheEnabled, boolean resolveGroupsAtCreation) throws AccessManagementException {
//public YarepUserManager(IdentityManager identityManager, Repository identitiesRepository) throws AccessManagementException {
this.identityManager = identityManager;
this.identitiesRepository = identitiesRepository;
this.cacheEnabled = cacheEnabled;
this.resolveGroupsAtCreation = resolveGroupsAtCreation;
}
/**
* Finds all user nodes in the repository and instantiates the users.
*
* Note re caching: If the UserManager is being instantiated only once at the startup of a server for instance, then the users are basically being cached (see getUser) and changes within the repository by a third pary application will not be noticed.
*
* @throws AccessManagementException
*/
private User[] loadUsersFromRepository() throws AccessManagementException {
log.info("Load users from repository '" + identitiesRepository.getConfigFile() + "'");
try {
Node usersParentNode = getUsersParentNode();
// TODO: There seems to be a bug such that users like ac-identities/http\:/michaelwechner.livejournal.com/.xml are not being detected either by getNodes() or isResource()!
Node[] userNodes = usersParentNode.getNodes();
DefaultConfigurationBuilder configBuilder = new DefaultConfigurationBuilder(true);
java.util.List<User> users = new java.util.ArrayList<User>();
for (int i = 0; i < userNodes.length; i++) {
if (userNodes[i].isResource()) {
try {
Configuration config = configBuilder.build(userNodes[i].getInputStream());
// also support identity for backwards compatibility
if (config.getName().equals(YarepUser.USER) || config.getName().equals("identity")) {
User user = constructUser(this.identityManager, userNodes[i]);
log.debug("User (re)loaded: " + userNodes[i].getName() + ", " + user.getID());
users.add(user);
}
} catch (Exception e) {
String errorMsg = "Could not create user from repository node: " + userNodes[i].getPath() + ": " + e.getMessage();
log.error(errorMsg, e);
// NOTE[et]: Do not fail here because other users may still be ok
//throw new AccessManagementException(errorMsg, e);
}
}
}
return (User[])users.toArray(new User[users.size()]);
} catch (RepositoryException e) {
String errorMsg = "Could not read users from repository: " + e.getMessage();
log.error(errorMsg, e);
throw new AccessManagementException(errorMsg, e);
}
}
/**
* Get user count.
*/
public int getUserCount() {
log.info("Load users from repository '" + identitiesRepository.getConfigFile() + "'");
try {
Node usersParentNode = getUsersParentNode();
Node[] userNodes = usersParentNode.getNodes();
int count = 0;
for (int i = 0; i < userNodes.length; i++) {
if (userNodes[i].getName().endsWith(SUFFIX) || userNodes[i].getName().endsWith(DEPRECATED_SUFFIX)) {
count++;
}
}
return count;
} catch(Exception e) {
return 0;
}
}
/**
* Loads a specific user from persistance storage into memory
*
* @param id User id
* @throws AccessManagementException
*/
protected synchronized void loadUserIntoCache(String id) throws AccessManagementException {
log.debug("Load user '" + id + "' from persistent repository '" + identitiesRepository.getName() + "' into cache.");
if (cachedUsers == null) {
log.warn("No users yet within memory. Initialize users hash map.");
cachedUsers = new HashMap();
}
if (cachedUsers.containsKey(id)) {
log.warn("User '" + id + "' already exists within memory, but will be reloaded!");
} else {
log.warn("User '" + id + "' does not exist wihtin memory yet, but will be loaded now!");
}
User user = getUserFromPersistentRepository(id);
if (user != null) {
cachedUsers.put(id, user);
}
}
/**
* @see org.wyona.security.core.api.UserManager#createUser(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
*/
public User createUser(String id, String name, String email, String password) throws AccessManagementException {
if (existsUser(id)) {
throw new AccessManagementException("User " + id + " already exists!");
}
try {
Node usersParentNode = getUsersParentNode();
YarepUser user = new YarepUser(this, identityManager.getGroupManager(), id, name);
if (email != null) {
user.setEmail(email);
}
if (password != null) {
user.setPassword(password);
} else {
log.warn("No password set for new user '" + id + "' (" + name + "), maybe user was pre-authenticated (e.g. using OpenID).");
}
user.setNode(usersParentNode.addNode(id + "." + SUFFIX, NodeType.RESOURCE));
if (resolveGroupsAtCreation) {
String[] groupIDs = user.getGroupIDs(false);
if (groupIDs != null) {
for (int i = 0; i < groupIDs.length; i++) {
log.debug("New user '" + id + "' belongs to group '" + groupIDs[i] + "' (This user probably existed before and groups were not cleaned at the time this user was deleted!)");
user.addGroup(groupIDs[i]);
}
}
}
user.save();
// INFO: Add to cache
if (cacheEnabled) {
loadUserIntoCache(id);
}
return user;
} catch (Exception e) {
log.error(e, e);
throw new AccessManagementException(e.getMessage(), e);
}
}
/**
* @see org.wyona.security.core.api.UserManager#createAlias(java.lang.String, java.lang.String)
*/
public User createAlias(String alias, String username) throws AccessManagementException {
try {
Node aliasesParentNode = getAliasesParentNode();
if (aliasesParentNode != null) {
if (aliasesParentNode.hasNode(alias + ".xml")) {
throw new AccessManagementException("Alias '" + alias + "' for user '" + username + "' already exists!");
} else {
YarepUser user = (YarepUser) getUser(username);
user.addAlias(alias);
Node aliasNode = aliasesParentNode.addNode(alias + ".xml", NodeType.RESOURCE);
String content = "<?xml version=\"1.0\"?>\n\n<alias " + PSEUDONYM + "=\"" + alias + "\" true-name=\"" + getTrueId(username) + "\"/>";
aliasNode.getOutputStream();
org.apache.commons.io.IOUtils.copy(new java.io.StringBufferInputStream(content), aliasNode.getOutputStream());
return getUser(getTrueId(alias));
}
} else {
log.warn("No aliases directory exists yet. Please consider to introduce one. ID will be returned as true ID.");
return null;
}
} catch(Exception e) {
throw new AccessManagementException(e.getMessage(), e);
}
}
/**
* Check if user exists within cache
*/
private boolean existsWithinCache(String userId) {
if (cacheEnabled && cachedUsers!= null && cachedUsers.containsKey(userId)) return true;
return false;
}
/**
* Check whether user exists inside persistent identities repository
* @param userId True ID of user
*/
private boolean existsWithinRepository(String userId) {
try {
Node usersParentNode = getUsersParentNode();
// Check .iml suffix in order to stay backwards compatible
if (usersParentNode.hasNode(userId + "." + DEPRECATED_SUFFIX)) {
log.warn("Deprecated user node path '" + userId + "." + DEPRECATED_SUFFIX + "' within repository '" + identitiesRepository.getName() + "'. Please upgrade by replacing the suffix '." + DEPRECATED_SUFFIX + "' by '." + SUFFIX + "'");
return true;
}
if (usersParentNode.hasNode(userId + "." + SUFFIX)) {
log.debug("User node '" + userId + "." + SUFFIX + "' exists inside persistent repository.");
return true;
}
} catch (Exception e) {
log.warn(e.getMessage(), e);
}
log.info("No such user '" + userId + "' within persistent repository.");
return false;
}
/**
* @see org.wyona.security.core.api.UserManager#existsUser(java.lang.String)
*/
public boolean existsUser(String id) throws AccessManagementException {
// Check the cache first
if (!existsWithinCache(id)) {
// TODO: What about checking whether id exists as alias?!
// Also check the repository
return existsWithinRepository(id);
}
return true;
}
/**
* Get user from repository
* @param id True ID of user
*/
private User getUserFromPersistentRepository(String id) throws AccessManagementException {
//log.debug("Get user '" + id + "' from persistent repository.");
if (existsWithinRepository(id)) {
try {
String nodeName = id + "." + SUFFIX;
Node usersParentNode = getUsersParentNode();
// Check for .iml suffix in order to stay backwards compatible
if (!usersParentNode.hasNode(nodeName)) {
nodeName = id + "." + DEPRECATED_SUFFIX;
}
return constructUser(this.identityManager, usersParentNode.getNode(nodeName));
} catch (Exception e) {
log.error(e, e);
throw new AccessManagementException(e.getMessage());
}
}
log.warn("No such user '" + id + "' inside persistent repository: " + this.identitiesRepository.getName());
return null;
}
/**
* @see org.wyona.security.core.api.UserManager#getUser(java.lang.String)
*/
public User getUser(String id) throws AccessManagementException {
if (cacheEnabled && existsWithinCache(id)) {
log.warn("Get user '" + id + "' from cache.");
return (User) cachedUsers.get(id);
} else {
return getUser(id, true);
}
}
/**
* @see org.wyona.security.core.api.UserManager#getUser(java.lang.String, boolean)
*/
public User getUser(String id, boolean refresh) throws AccessManagementException {
if (refresh) {
/*
log.warn("Refresh of group manager after reloading all users, such that user '" + id + "' has access to a refreshed group manager!");
((YarepGroupManager)identityManager.getGroupManager()).loadGroups();
*/
if (cacheEnabled) {
log.warn("Update user '" + id + "' within cache.");
loadUserIntoCache(id);
return (User) cachedUsers.get(id);
} else {
return getUserFromPersistentRepository(id);
}
} else {
if (cacheEnabled) {
if (!existsWithinCache(id)) {
log.warn("User cache does not exist yet, hence user '" + id + "' will be loaded into cache ...");
loadUserIntoCache(id);
}
return (User) cachedUsers.get(id);
} else {
log.warn("Cache is disabled, hence get user '" + id + "' from repository");
return getUserFromPersistentRepository(id);
}
}
}
/**
* @see org.wyona.security.core.api.UserManager#getUsers()
*/
public User[] getUsers() throws AccessManagementException {
log.warn("This method does not scale well. Rather use an iterator!");
if (cacheEnabled && cachedUsers != null) {
return (User[]) cachedUsers.values().toArray(new User[cachedUsers.size()]);
} else {
return getUsers(true);
}
}
/**
* @see org.wyona.security.core.api.UserManager#getAllUsers()
*/
public java.util.Iterator<User> getAllUsers() throws AccessManagementException {
return new YarepUsersIterator(identityManager, identitiesRepository, cacheEnabled, resolveGroupsAtCreation);
}
/**
* @see org.wyona.security.core.api.UserManager#getUsers(String)
*/
public java.util.Iterator<User> getUsers(String query) throws AccessManagementException {
return new YarepUsersIterator(identityManager, identitiesRepository, cacheEnabled, resolveGroupsAtCreation, query);
}
/**
* @see org.wyona.security.core.api.UserManager#getUsers(boolean)
*/
public User[] getUsers(boolean refresh) throws AccessManagementException {
if (refresh) {
return loadUsersFromRepository();
} else {
if (cacheEnabled) {
if (cachedUsers == null) {
log.warn("User cache does not exist yet, hence users will be loaded into cache ...");
cachedUsers = new HashMap();
User[] users = loadUsersFromRepository();
for (int i = 0; i < users.length; i++) {
cachedUsers.put(users[i].getID(), users[i]);
}
}
return (User[]) cachedUsers.values().toArray(new User[cachedUsers.size()]);
} else {
log.warn("Cache is disabled, hence get users from repository");
return loadUsersFromRepository();
}
}
/*
if(refresh){
loadUsers();
log.info("Refresh of group manager after reloading all users, such that users have access to a refreshed group manager!");
((YarepGroupManager)identityManager.getGroupManager()).loadGroups();
}
return getUsers();
*/
}
/**
* @see org.wyona.security.core.api.UserManager#removeUser(java.lang.String)
*/
public void removeUser(String id) throws AccessManagementException {
if (!existsUser(id)) {
throw new AccessManagementException("User " + id + " does not exist.");
}
User user = getUser(id);
Group[] groups = user.getGroups();
for (int i = 0; i < groups.length; i++) {
groups[i].removeMember(user);
groups[i].save();
}
String[] aliases = ((YarepUser) user).getAliases();
for (int i = 0; i < aliases.length; i++) {
removeAlias(aliases[i]);
}
if (cacheEnabled && existsWithinCache(id)) {
cachedUsers.remove(id);
}
user.delete();
}
/**
* @see org.wyona.security.core.api.UserManager#removeAlias(java.lang.String)
*/
public void removeAlias(String alias) throws AccessManagementException {
try {
Node aliasesParentNode = getAliasesParentNode();
if (aliasesParentNode != null) {
if (aliasesParentNode.hasNode(alias + ".xml")) {
YarepUser user = (YarepUser) getUser(getTrueId(alias));
String[] aliases = user.getAliases();
if (aliases != null) {
log.debug("Check whether '" + alias + "' is the last alias of a specific username!");
if (aliases.length == 1) {
if (aliases[0].equals(alias)) {
log.warn("Alias '" + alias + "' is the only alias of user '" + user.getID() + "'!");
} else {
log.warn("Alias '" + aliases[0] + "' is the only alias of user '" + user.getID() + "', but does not match with '" + alias + "', the one to be removed!");
}
} else {
log.info("User '" + user.getID() + "' has " + aliases.length + " aliases.");
for (int i = 0; i < aliases.length; i++) {
log.debug("Alias: " + aliases[i]);
}
}
user.removeAlias(alias);
Node aliasNode = aliasesParentNode.getNode(alias + ".xml");
aliasNode.delete();
} else {
log.warn("User '" + user.getID() + "' has no aliases!");
}
} else {
log.warn("No alias found for id '" + alias + "'!");
}
} else {
log.warn("No aliases directory exists yet. Please consider to introduce one. ID will be returned as true ID.");
}
} catch(Exception e) {
throw new AccessManagementException(e.getMessage(), e);
}
}
/**
* @see org.wyona.security.core.api.UserManager#existsAlias(java.lang.String)
*/
public boolean existsAlias(String alias) throws AccessManagementException {
try {
Node aliasesParentNode = getAliasesParentNode();
if (aliasesParentNode != null) {
if (aliasesParentNode.hasNode(alias + ".xml")) {
return true;
} else {
return false;
}
} else {
log.warn("No aliases directory exists yet. Please consider to introduce one. ID will be returned as true ID.");
return false;
}
} catch(Exception e) {
throw new AccessManagementException(e.getMessage(), e);
}
}
/**
* Gets the repository node which is the parent node of all user nodes.
*
* @return node which is the parent of all user nodes.
* @throws NoSuchNodeException
* @throws RepositoryException
*/
protected Node getUsersParentNode() throws NoSuchNodeException, RepositoryException {
if (this.identitiesRepository.existsNode("/users")) {
return this.identitiesRepository.getNode("/users");
}
log.warn("Fallback to root node (Repository: " + identitiesRepository.getName() + ") for backwards compatibility. Please upgrade by introducing a /users node!");
return this.identitiesRepository.getNode("/");
}
/**
* Gets the repository node which is the parent node of all aliases nodes.
*
* @return node which is the parent of all aliases nodes.
* @throws NoSuchNodeException
* @throws RepositoryException
*/
protected Node getAliasesParentNode() throws NoSuchNodeException, RepositoryException {
if (this.identitiesRepository.existsNode("/aliases")) {
return this.identitiesRepository.getNode("/aliases");
} else {
return org.wyona.yarep.util.YarepUtil.addNodes(this.identitiesRepository, "/aliases", org.wyona.yarep.core.NodeType.COLLECTION);
}
}
/**
* Override in subclasses
* @param node Repository node of user
*/
protected User constructUser(IdentityManager identityManager, Node node) throws AccessManagementException{
YarepUser user = new YarepUser(this, identityManager.getGroupManager(), node);
if(user.isUpgraded()) {
user.save();
}
return user;
}
/**
*
*/
protected boolean isCacheEnabled() {
return cacheEnabled;
}
/**
* @see org.wyona.security.core.api.UserManager#getTrueId(String)
*/
public String getTrueId(String id) throws AccessManagementException {
try {
Node aliasesParentNode = getAliasesParentNode();
if (aliasesParentNode != null) {
log.debug("Get true ID from alias '" + id + "'...");
if (aliasesParentNode.hasNode(id + ".xml")) {
Node aliasNode = aliasesParentNode.getNode(id + ".xml");
DefaultConfigurationBuilder configBuilder = new DefaultConfigurationBuilder(true);
Configuration config = configBuilder.build(aliasNode.getInputStream());
String pseudo = config.getAttribute(PSEUDONYM, "NULL");
if (!pseudo.toLowerCase().equals(id.toLowerCase())) {
throw new AccessManagementException("Pseudonym attribute '" + pseudo + "' of node '" + aliasNode.getName() + "' does not match Id '" + id + "'.");
}
String trueId = config.getAttribute("true-name", "NULL");
log.debug(String.format("Pseudonym: %s, true-ID: %s", pseudo, trueId));
return trueId;
} else {
log.debug("No alias found for id '" + id + "', hence return this id as true ID");
return id;
}
} else {
log.warn("No aliases directory exists yet. Please consider to introduce one. ID will be returned as true ID.");
return id;
}
} catch(Exception e) {
throw new AccessManagementException(e.getMessage(), e);
}
}
}