1 package org
.argeo
.osgi
.useradmin
;
3 import static org
.argeo
.util
.naming
.LdapAttrs
.objectClass
;
4 import static org
.argeo
.util
.naming
.LdapObjs
.extensibleObject
;
5 import static org
.argeo
.util
.naming
.LdapObjs
.inetOrgPerson
;
6 import static org
.argeo
.util
.naming
.LdapObjs
.organizationalPerson
;
7 import static org
.argeo
.util
.naming
.LdapObjs
.person
;
8 import static org
.argeo
.util
.naming
.LdapObjs
.top
;
11 import java
.security
.PrivilegedAction
;
12 import java
.util
.ArrayList
;
13 import java
.util
.Dictionary
;
14 import java
.util
.Hashtable
;
15 import java
.util
.Iterator
;
16 import java
.util
.List
;
18 import javax
.naming
.Context
;
19 import javax
.naming
.InvalidNameException
;
20 import javax
.naming
.directory
.Attributes
;
21 import javax
.naming
.directory
.BasicAttribute
;
22 import javax
.naming
.directory
.BasicAttributes
;
23 import javax
.naming
.ldap
.LdapName
;
24 import javax
.naming
.ldap
.Rdn
;
25 import javax
.security
.auth
.Subject
;
26 import javax
.security
.auth
.kerberos
.KerberosTicket
;
28 import org
.argeo
.util
.CurrentSubject
;
29 import org
.argeo
.util
.directory
.DirectoryConf
;
30 import org
.argeo
.util
.directory
.DirectoryDigestUtils
;
31 import org
.argeo
.util
.directory
.HierarchyUnit
;
32 import org
.argeo
.util
.directory
.ldap
.AbstractLdapDirectory
;
33 import org
.argeo
.util
.directory
.ldap
.LdapDao
;
34 import org
.argeo
.util
.directory
.ldap
.LdapEntry
;
35 import org
.argeo
.util
.directory
.ldap
.LdapEntryWorkingCopy
;
36 import org
.argeo
.util
.directory
.ldap
.LdapNameUtils
;
37 import org
.argeo
.util
.directory
.ldap
.LdifDao
;
38 import org
.osgi
.framework
.Filter
;
39 import org
.osgi
.framework
.FrameworkUtil
;
40 import org
.osgi
.framework
.InvalidSyntaxException
;
41 import org
.osgi
.service
.useradmin
.Authorization
;
42 import org
.osgi
.service
.useradmin
.Role
;
43 import org
.osgi
.service
.useradmin
.User
;
44 import org
.osgi
.service
.useradmin
.UserAdmin
;
46 /** Base class for a {@link UserDirectory}. */
47 public class DirectoryUserAdmin
extends AbstractLdapDirectory
implements UserAdmin
, UserDirectory
{
49 private UserAdmin externalRoles
;
50 // private List<String> indexedUserProperties = Arrays
51 // .asList(new String[] { LdapAttrs.uid.name(), LdapAttrs.mail.name(),
52 // LdapAttrs.cn.name() });
55 // private TransactionManager transactionManager;
56 public DirectoryUserAdmin(URI uriArg
, Dictionary
<String
, ?
> props
) {
57 this(uriArg
, props
, false);
60 public DirectoryUserAdmin(URI uriArg
, Dictionary
<String
, ?
> props
, boolean scoped
) {
61 super(uriArg
, props
, scoped
);
64 public DirectoryUserAdmin(Dictionary
<String
, ?
> props
) {
72 protected AbstractLdapDirectory
scope(User user
) {
73 if (getDirectoryDao() instanceof LdapDao
) {
74 return scopeLdap(user
);
75 } else if (getDirectoryDao() instanceof LdifDao
) {
76 return scopeLdif(user
);
78 throw new IllegalStateException("Unsupported DAO " + getDirectoryDao().getClass());
82 protected DirectoryUserAdmin
scopeLdap(User user
) {
83 Dictionary
<String
, Object
> credentials
= user
.getCredentials();
84 String username
= (String
) credentials
.get(SHARED_STATE_USERNAME
);
86 username
= user
.getName();
87 Dictionary
<String
, Object
> properties
= cloneConfigProperties();
88 properties
.put(Context
.SECURITY_PRINCIPAL
, username
.toString());
89 Object pwdCred
= credentials
.get(SHARED_STATE_PASSWORD
);
90 byte[] pwd
= (byte[]) pwdCred
;
92 char[] password
= DirectoryDigestUtils
.bytesToChars(pwd
);
93 properties
.put(Context
.SECURITY_CREDENTIALS
, new String(password
));
95 properties
.put(Context
.SECURITY_AUTHENTICATION
, "GSSAPI");
97 DirectoryUserAdmin scopedDirectory
= new DirectoryUserAdmin(null, properties
, true);
98 scopedDirectory
.init();
99 return scopedDirectory
;
102 protected DirectoryUserAdmin
scopeLdif(User user
) {
103 Dictionary
<String
, Object
> credentials
= user
.getCredentials();
104 String username
= (String
) credentials
.get(SHARED_STATE_USERNAME
);
105 if (username
== null)
106 username
= user
.getName();
107 Object pwdCred
= credentials
.get(SHARED_STATE_PASSWORD
);
108 byte[] pwd
= (byte[]) pwdCred
;
110 char[] password
= DirectoryDigestUtils
.bytesToChars(pwd
);
111 User directoryUser
= (User
) getRole(username
);
112 if (!directoryUser
.hasCredential(null, password
))
113 throw new IllegalStateException("Invalid credentials");
115 throw new IllegalStateException("Password is required");
117 Dictionary
<String
, Object
> properties
= cloneConfigProperties();
118 properties
.put(DirectoryConf
.readOnly
.name(), "true");
119 DirectoryUserAdmin scopedUserAdmin
= new DirectoryUserAdmin(null, properties
, true);
120 // scopedUserAdmin.groups = Collections.unmodifiableNavigableMap(groups);
121 // scopedUserAdmin.users = Collections.unmodifiableNavigableMap(users);
122 // FIXME do it better
123 ((LdifDao
) getDirectoryDao()).scope((LdifDao
) scopedUserAdmin
.getDirectoryDao());
124 scopedUserAdmin
.init();
125 return scopedUserAdmin
;
129 public String
getRolePath(Role role
) {
130 return nameToRelativePath(LdapNameUtils
.toLdapName(role
.getName()));
134 public String
getRoleSimpleName(Role role
) {
135 LdapName dn
= LdapNameUtils
.toLdapName(role
.getName());
136 String name
= LdapNameUtils
.getLastRdnValue(dn
);
141 public Role
getRoleByPath(String path
) {
142 LdapEntry entry
= doGetRole(pathToName(path
));
143 if (!(entry
instanceof Role
)) {
145 // throw new IllegalStateException("Path must be a UserAdmin Role.");
151 protected List
<Role
> getAllRoles(DirectoryUser user
) {
152 List
<Role
> allRoles
= new ArrayList
<Role
>();
154 collectRoles((LdapEntry
) user
, allRoles
);
157 collectAnonymousRoles(allRoles
);
161 private void collectRoles(LdapEntry user
, List
<Role
> allRoles
) {
162 List
<LdapEntry
> allEntries
= new ArrayList
<>();
163 LdapEntry entry
= user
;
164 collectGroups(entry
, allEntries
);
165 for (LdapEntry e
: allEntries
) {
166 if (e
instanceof Role
)
167 allRoles
.add((Role
) e
);
169 // Attributes attrs = user.getAttributes();
170 // // TODO centralize attribute name
171 // Attribute memberOf = attrs.get(LdapAttrs.memberOf.name());
172 // // if user belongs to this directory, we only check memberOf
173 // if (memberOf != null && user.getDn().startsWith(getBaseDn())) {
175 // NamingEnumeration<?> values = memberOf.getAll();
176 // while (values.hasMore()) {
177 // Object value = values.next();
178 // LdapName groupDn = new LdapName(value.toString());
179 // DirectoryUser group = doGetRole(groupDn);
180 // if (group != null)
181 // allRoles.add(group);
183 // } catch (NamingException e) {
184 // throw new IllegalStateException("Cannot get memberOf groups for " + user, e);
187 // for (LdapName groupDn : getDirectoryDao().getDirectGroups(user.getDn())) {
188 // // TODO check for loops
189 // DirectoryUser group = doGetRole(groupDn);
190 // if (group != null) {
191 // allRoles.add(group);
192 // collectRoles(group, allRoles);
198 private void collectAnonymousRoles(List
<Role
> allRoles
) {
199 // TODO gather anonymous roles
204 public Role
getRole(String name
) {
205 return (Role
) doGetRole(toLdapName(name
));
209 public Role
[] getRoles(String filter
) throws InvalidSyntaxException
{
210 List
<?
extends Role
> res
= getRoles(getBaseDn(), filter
, true);
211 return res
.toArray(new Role
[res
.size()]);
214 List
<DirectoryUser
> getRoles(LdapName searchBase
, String filter
, boolean deep
) throws InvalidSyntaxException
{
215 LdapEntryWorkingCopy wc
= getWorkingCopy();
216 // Filter f = filter != null ? FrameworkUtil.createFilter(filter) : null;
217 List
<LdapEntry
> searchRes
= getDirectoryDao().doGetEntries(searchBase
, filter
, deep
);
218 List
<DirectoryUser
> res
= new ArrayList
<>();
219 for (LdapEntry entry
: searchRes
)
220 res
.add((DirectoryUser
) entry
);
222 for (Iterator
<DirectoryUser
> it
= res
.iterator(); it
.hasNext();) {
223 DirectoryUser user
= (DirectoryUser
) it
.next();
224 LdapName dn
= LdapNameUtils
.toLdapName(user
.getName());
225 if (wc
.getDeletedData().containsKey(dn
))
228 Filter f
= filter
!= null ? FrameworkUtil
.createFilter(filter
) : null;
229 for (LdapEntry ldapEntry
: wc
.getNewData().values()) {
230 DirectoryUser user
= (DirectoryUser
) ldapEntry
;
231 if (f
== null || f
.match(user
.getProperties()))
234 // no need to check modified users,
235 // since doGetRoles was already based on the modified attributes
238 // if non deep we also search users and groups
241 // if (!(searchBase.endsWith(new LdapName(getUserBase()))
242 // || searchBase.endsWith(new LdapName(getGroupBase())))) {
243 // LdapName usersBase = (LdapName) ((LdapName) searchBase.clone()).add(getUserBase());
244 // res.addAll(getRoles(usersBase, filter, false));
245 // LdapName groupsBase = (LdapName) ((LdapName) searchBase.clone()).add(getGroupBase());
246 // res.addAll(getRoles(groupsBase, filter, false));
248 // } catch (InvalidNameException e) {
249 // throw new IllegalStateException("Cannot search users and groups", e);
256 public User
getUser(String key
, String value
) {
257 // TODO check value null or empty
258 List
<DirectoryUser
> collectedUsers
= new ArrayList
<DirectoryUser
>();
260 doGetUser(key
, value
, collectedUsers
);
262 throw new IllegalArgumentException("Key cannot be null");
265 if (collectedUsers
.size() == 1) {
266 return collectedUsers
.get(0);
267 } else if (collectedUsers
.size() > 1) {
268 // log.warn(collectedUsers.size() + " users for " + (key != null ? key + "=" :
274 protected void doGetUser(String key
, String value
, List
<DirectoryUser
> collectedUsers
) {
275 String f
= "(" + key
+ "=" + value
+ ")";
276 List
<LdapEntry
> users
= getDirectoryDao().doGetEntries(getBaseDn(), f
, true);
277 for (LdapEntry entry
: users
)
278 collectedUsers
.add((DirectoryUser
) entry
);
282 public Authorization
getAuthorization(User user
) {
283 if (user
== null) {// anonymous
284 return new LdifAuthorization(user
, getAllRoles(null));
286 LdapName userName
= toLdapName(user
.getName());
287 if (isExternal(userName
) && user
instanceof LdapEntry
) {
288 List
<Role
> allRoles
= new ArrayList
<Role
>();
289 collectRoles((LdapEntry
) user
, allRoles
);
290 return new LdifAuthorization(user
, allRoles
);
293 Subject currentSubject
= CurrentSubject
.current();
294 if (currentSubject
!= null //
295 && getRealm().isPresent() //
296 && !currentSubject
.getPrivateCredentials(Authorization
.class).isEmpty() //
297 && !currentSubject
.getPrivateCredentials(KerberosTicket
.class).isEmpty()) //
299 // TODO not only Kerberos but also bind scope with kept password ?
300 Authorization auth
= currentSubject
.getPrivateCredentials(Authorization
.class).iterator().next();
301 // bind with authenticating user
302 DirectoryUserAdmin scopedUserAdmin
= Subject
.doAs(currentSubject
,
303 (PrivilegedAction
<DirectoryUserAdmin
>) () -> (DirectoryUserAdmin
) scope(
304 new AuthenticatingUser(auth
.getName(), new Hashtable
<>())));
305 return getAuthorizationFromScoped(scopedUserAdmin
, user
);
308 if (user
instanceof DirectoryUser
) {
309 return new LdifAuthorization(user
, getAllRoles((DirectoryUser
) user
));
311 // bind with authenticating user
312 DirectoryUserAdmin scopedUserAdmin
= (DirectoryUserAdmin
) scope(user
);
313 return getAuthorizationFromScoped(scopedUserAdmin
, user
);
318 private Authorization
getAuthorizationFromScoped(DirectoryUserAdmin scopedUserAdmin
, User user
) {
320 DirectoryUser directoryUser
= (DirectoryUser
) scopedUserAdmin
.getRole(user
.getName());
321 if (directoryUser
== null)
322 throw new IllegalStateException("No scoped user found for " + user
);
323 LdifAuthorization authorization
= new LdifAuthorization(directoryUser
,
324 scopedUserAdmin
.getAllRoles(directoryUser
));
325 return authorization
;
327 scopedUserAdmin
.destroy();
332 public Role
createRole(String name
, int type
) {
334 LdapEntryWorkingCopy wc
= getWorkingCopy();
335 LdapName dn
= toLdapName(name
);
336 if ((getDirectoryDao().entryExists(dn
) && !wc
.getDeletedData().containsKey(dn
))
337 || wc
.getNewData().containsKey(dn
))
338 throw new IllegalArgumentException("Already a role " + name
);
339 BasicAttributes attrs
= new BasicAttributes(true);
340 // attrs.put(LdifName.dn.name(), dn.toString());
341 Rdn nameRdn
= dn
.getRdn(dn
.size() - 1);
342 // TODO deal with multiple attr RDN
343 attrs
.put(nameRdn
.getType(), nameRdn
.getValue());
344 if (wc
.getDeletedData().containsKey(dn
)) {
345 wc
.getDeletedData().remove(dn
);
346 wc
.getModifiedData().put(dn
, attrs
);
347 return getRole(name
);
349 wc
.getModifiedData().put(dn
, attrs
);
350 LdapEntry newRole
= doCreateRole(dn
, type
, attrs
);
351 wc
.getNewData().put(dn
, newRole
);
352 return (Role
) newRole
;
356 private LdapEntry
doCreateRole(LdapName dn
, int type
, Attributes attrs
) {
358 BasicAttribute objClass
= new BasicAttribute(objectClass
.name());
359 if (type
== Role
.USER
) {
360 String userObjClass
= getUserObjectClass();
361 objClass
.add(userObjClass
);
362 if (inetOrgPerson
.name().equals(userObjClass
)) {
363 objClass
.add(organizationalPerson
.name());
364 objClass
.add(person
.name());
365 } else if (organizationalPerson
.name().equals(userObjClass
)) {
366 objClass
.add(person
.name());
368 objClass
.add(top
.name());
369 objClass
.add(extensibleObject
.name());
371 newRole
= newUser(dn
);
372 } else if (type
== Role
.GROUP
) {
373 String groupObjClass
= getGroupObjectClass();
374 objClass
.add(groupObjClass
);
375 // objClass.add(LdifName.extensibleObject.name());
376 objClass
.add(top
.name());
378 newRole
= newGroup(dn
);
380 throw new IllegalArgumentException("Unsupported type " + type
);
385 public boolean removeRole(String name
) {
386 return removeEntry(LdapNameUtils
.toLdapName(name
));
388 // LdapEntryWorkingCopy wc = getWorkingCopy();
389 // LdapName dn = toLdapName(name);
390 // boolean actuallyDeleted;
391 // if (getDirectoryDao().daoHasEntry(dn) || wc.getNewData().containsKey(dn)) {
392 // DirectoryUser user = (DirectoryUser) getRole(name);
393 // wc.getDeletedData().put(dn, user);
394 // actuallyDeleted = true;
395 // } else {// just removing from groups (e.g. system roles)
396 // actuallyDeleted = false;
398 // for (LdapName groupDn : getDirectoryDao().getDirectGroups(dn)) {
399 // LdapEntry group = doGetRole(groupDn);
400 // group.getAttributes().get(getMemberAttributeId()).remove(dn.toString());
402 // return actuallyDeleted;
409 public HierarchyUnit
getHierarchyUnit(Role role
) {
410 LdapName dn
= LdapNameUtils
.toLdapName(role
.getName());
411 LdapName huDn
= LdapNameUtils
.getParent(dn
);
412 HierarchyUnit hierarchyUnit
= getDirectoryDao().doGetHierarchyUnit(huDn
);
413 if (hierarchyUnit
== null)
414 throw new IllegalStateException("No hierarchy unit found for " + role
);
415 return hierarchyUnit
;
419 public Iterable
<?
extends Role
> getHierarchyUnitRoles(HierarchyUnit hierarchyUnit
, String filter
, boolean deep
) {
420 LdapName dn
= LdapNameUtils
.toLdapName(hierarchyUnit
.getBase());
422 return getRoles(dn
, filter
, deep
);
423 } catch (InvalidSyntaxException e
) {
424 throw new IllegalArgumentException("Cannot filter " + filter
+ " " + dn
, e
);
431 protected LdapEntry
newUser(LdapName name
) {
432 // TODO support devices, applications, etc.
433 return new LdifUser(this, name
);
436 protected LdapEntry
newGroup(LdapName name
) {
437 return new LdifGroup(this, name
);
442 protected UserAdmin
getExternalRoles() {
443 return externalRoles
;
446 public void setExternalRoles(UserAdmin externalRoles
) {
447 this.externalRoles
= externalRoles
;
450 // public void setTransactionManager(TransactionManager transactionManager) {
451 // this.transactionManager = transactionManager;
457 static LdapName
toLdapName(String name
) {
459 return new LdapName(name
);
460 } catch (InvalidNameException e
) {
461 throw new IllegalArgumentException(name
+ " is not an LDAP name", e
);