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
)) {
144 throw new IllegalStateException("Path must be a UserAdmin Role.");
150 protected List
<Role
> getAllRoles(DirectoryUser user
) {
151 List
<Role
> allRoles
= new ArrayList
<Role
>();
153 collectRoles((LdapEntry
) user
, allRoles
);
156 collectAnonymousRoles(allRoles
);
160 private void collectRoles(LdapEntry user
, List
<Role
> allRoles
) {
161 List
<LdapEntry
> allEntries
= new ArrayList
<>();
162 LdapEntry entry
= user
;
163 collectGroups(entry
, allEntries
);
164 for (LdapEntry e
: allEntries
) {
165 if (e
instanceof Role
)
166 allRoles
.add((Role
) e
);
168 // Attributes attrs = user.getAttributes();
169 // // TODO centralize attribute name
170 // Attribute memberOf = attrs.get(LdapAttrs.memberOf.name());
171 // // if user belongs to this directory, we only check memberOf
172 // if (memberOf != null && user.getDn().startsWith(getBaseDn())) {
174 // NamingEnumeration<?> values = memberOf.getAll();
175 // while (values.hasMore()) {
176 // Object value = values.next();
177 // LdapName groupDn = new LdapName(value.toString());
178 // DirectoryUser group = doGetRole(groupDn);
179 // if (group != null)
180 // allRoles.add(group);
182 // } catch (NamingException e) {
183 // throw new IllegalStateException("Cannot get memberOf groups for " + user, e);
186 // for (LdapName groupDn : getDirectoryDao().getDirectGroups(user.getDn())) {
187 // // TODO check for loops
188 // DirectoryUser group = doGetRole(groupDn);
189 // if (group != null) {
190 // allRoles.add(group);
191 // collectRoles(group, allRoles);
197 private void collectAnonymousRoles(List
<Role
> allRoles
) {
198 // TODO gather anonymous roles
203 public Role
getRole(String name
) {
204 return (Role
) doGetRole(toLdapName(name
));
208 public Role
[] getRoles(String filter
) throws InvalidSyntaxException
{
209 List
<?
extends Role
> res
= getRoles(getBaseDn(), filter
, true);
210 return res
.toArray(new Role
[res
.size()]);
213 List
<DirectoryUser
> getRoles(LdapName searchBase
, String filter
, boolean deep
) throws InvalidSyntaxException
{
214 LdapEntryWorkingCopy wc
= getWorkingCopy();
215 // Filter f = filter != null ? FrameworkUtil.createFilter(filter) : null;
216 List
<LdapEntry
> searchRes
= getDirectoryDao().doGetEntries(searchBase
, filter
, deep
);
217 List
<DirectoryUser
> res
= new ArrayList
<>();
218 for (LdapEntry entry
: searchRes
)
219 res
.add((DirectoryUser
) entry
);
221 for (Iterator
<DirectoryUser
> it
= res
.iterator(); it
.hasNext();) {
222 DirectoryUser user
= (DirectoryUser
) it
.next();
223 LdapName dn
= LdapNameUtils
.toLdapName(user
.getName());
224 if (wc
.getDeletedData().containsKey(dn
))
227 Filter f
= filter
!= null ? FrameworkUtil
.createFilter(filter
) : null;
228 for (LdapEntry ldapEntry
: wc
.getNewData().values()) {
229 DirectoryUser user
= (DirectoryUser
) ldapEntry
;
230 if (f
== null || f
.match(user
.getProperties()))
233 // no need to check modified users,
234 // since doGetRoles was already based on the modified attributes
237 // if non deep we also search users and groups
240 // if (!(searchBase.endsWith(new LdapName(getUserBase()))
241 // || searchBase.endsWith(new LdapName(getGroupBase())))) {
242 // LdapName usersBase = (LdapName) ((LdapName) searchBase.clone()).add(getUserBase());
243 // res.addAll(getRoles(usersBase, filter, false));
244 // LdapName groupsBase = (LdapName) ((LdapName) searchBase.clone()).add(getGroupBase());
245 // res.addAll(getRoles(groupsBase, filter, false));
247 // } catch (InvalidNameException e) {
248 // throw new IllegalStateException("Cannot search users and groups", e);
255 public User
getUser(String key
, String value
) {
256 // TODO check value null or empty
257 List
<DirectoryUser
> collectedUsers
= new ArrayList
<DirectoryUser
>();
259 doGetUser(key
, value
, collectedUsers
);
261 throw new IllegalArgumentException("Key cannot be null");
264 if (collectedUsers
.size() == 1) {
265 return collectedUsers
.get(0);
266 } else if (collectedUsers
.size() > 1) {
267 // log.warn(collectedUsers.size() + " users for " + (key != null ? key + "=" :
273 protected void doGetUser(String key
, String value
, List
<DirectoryUser
> collectedUsers
) {
274 String f
= "(" + key
+ "=" + value
+ ")";
275 List
<LdapEntry
> users
= getDirectoryDao().doGetEntries(getBaseDn(), f
, true);
276 for (LdapEntry entry
: users
)
277 collectedUsers
.add((DirectoryUser
) entry
);
281 public Authorization
getAuthorization(User user
) {
282 if (user
== null) {// anonymous
283 return new LdifAuthorization(user
, getAllRoles(null));
285 LdapName userName
= toLdapName(user
.getName());
286 if (isExternal(userName
) && user
instanceof LdapEntry
) {
287 List
<Role
> allRoles
= new ArrayList
<Role
>();
288 collectRoles((LdapEntry
) user
, allRoles
);
289 return new LdifAuthorization(user
, allRoles
);
292 Subject currentSubject
= CurrentSubject
.current();
293 if (currentSubject
!= null //
294 && !currentSubject
.getPrivateCredentials(Authorization
.class).isEmpty() //
295 && !currentSubject
.getPrivateCredentials(KerberosTicket
.class).isEmpty()) {
296 // TODO not only Kerberos but also bind scope with kept password ?
297 Authorization auth
= currentSubject
.getPrivateCredentials(Authorization
.class).iterator().next();
298 // bind with authenticating user
299 DirectoryUserAdmin scopedUserAdmin
= Subject
.doAs(currentSubject
,
300 (PrivilegedAction
<DirectoryUserAdmin
>) () -> (DirectoryUserAdmin
) scope(
301 new AuthenticatingUser(auth
.getName(), new Hashtable
<>())));
302 return getAuthorizationFromScoped(scopedUserAdmin
, user
);
305 if (user
instanceof DirectoryUser
) {
306 return new LdifAuthorization(user
, getAllRoles((DirectoryUser
) user
));
308 // bind with authenticating user
309 DirectoryUserAdmin scopedUserAdmin
= (DirectoryUserAdmin
) scope(user
);
310 return getAuthorizationFromScoped(scopedUserAdmin
, user
);
315 private Authorization
getAuthorizationFromScoped(DirectoryUserAdmin scopedUserAdmin
, User user
) {
317 DirectoryUser directoryUser
= (DirectoryUser
) scopedUserAdmin
.getRole(user
.getName());
318 if (directoryUser
== null)
319 throw new IllegalStateException("No scoped user found for " + user
);
320 LdifAuthorization authorization
= new LdifAuthorization(directoryUser
,
321 scopedUserAdmin
.getAllRoles(directoryUser
));
322 return authorization
;
324 scopedUserAdmin
.destroy();
329 public Role
createRole(String name
, int type
) {
331 LdapEntryWorkingCopy wc
= getWorkingCopy();
332 LdapName dn
= toLdapName(name
);
333 if ((getDirectoryDao().entryExists(dn
) && !wc
.getDeletedData().containsKey(dn
))
334 || wc
.getNewData().containsKey(dn
))
335 throw new IllegalArgumentException("Already a role " + name
);
336 BasicAttributes attrs
= new BasicAttributes(true);
337 // attrs.put(LdifName.dn.name(), dn.toString());
338 Rdn nameRdn
= dn
.getRdn(dn
.size() - 1);
339 // TODO deal with multiple attr RDN
340 attrs
.put(nameRdn
.getType(), nameRdn
.getValue());
341 if (wc
.getDeletedData().containsKey(dn
)) {
342 wc
.getDeletedData().remove(dn
);
343 wc
.getModifiedData().put(dn
, attrs
);
344 return getRole(name
);
346 wc
.getModifiedData().put(dn
, attrs
);
347 LdapEntry newRole
= newRole(dn
, type
, attrs
);
348 wc
.getNewData().put(dn
, newRole
);
349 return (Role
) newRole
;
353 protected LdapEntry
newRole(LdapName dn
, int type
, Attributes attrs
) {
355 BasicAttribute objClass
= new BasicAttribute(objectClass
.name());
356 if (type
== Role
.USER
) {
357 String userObjClass
= getUserObjectClass();
358 objClass
.add(userObjClass
);
359 if (inetOrgPerson
.name().equals(userObjClass
)) {
360 objClass
.add(organizationalPerson
.name());
361 objClass
.add(person
.name());
362 } else if (organizationalPerson
.name().equals(userObjClass
)) {
363 objClass
.add(person
.name());
365 objClass
.add(top
.name());
366 objClass
.add(extensibleObject
.name());
368 newRole
= newUser(dn
, attrs
);
369 } else if (type
== Role
.GROUP
) {
370 String groupObjClass
= getGroupObjectClass();
371 objClass
.add(groupObjClass
);
372 // objClass.add(LdifName.extensibleObject.name());
373 objClass
.add(top
.name());
375 newRole
= newGroup(dn
, attrs
);
377 throw new IllegalArgumentException("Unsupported type " + type
);
382 public boolean removeRole(String name
) {
383 return removeEntry(LdapNameUtils
.toLdapName(name
));
385 // LdapEntryWorkingCopy wc = getWorkingCopy();
386 // LdapName dn = toLdapName(name);
387 // boolean actuallyDeleted;
388 // if (getDirectoryDao().daoHasEntry(dn) || wc.getNewData().containsKey(dn)) {
389 // DirectoryUser user = (DirectoryUser) getRole(name);
390 // wc.getDeletedData().put(dn, user);
391 // actuallyDeleted = true;
392 // } else {// just removing from groups (e.g. system roles)
393 // actuallyDeleted = false;
395 // for (LdapName groupDn : getDirectoryDao().getDirectGroups(dn)) {
396 // LdapEntry group = doGetRole(groupDn);
397 // group.getAttributes().get(getMemberAttributeId()).remove(dn.toString());
399 // return actuallyDeleted;
406 public HierarchyUnit
getHierarchyUnit(Role role
) {
407 LdapName dn
= LdapNameUtils
.toLdapName(role
.getName());
408 LdapName huDn
= LdapNameUtils
.getParent(dn
);
409 HierarchyUnit hierarchyUnit
= getDirectoryDao().doGetHierarchyUnit(huDn
);
410 if (hierarchyUnit
== null)
411 throw new IllegalStateException("No hierarchy unit found for " + role
);
412 return hierarchyUnit
;
416 public Iterable
<?
extends Role
> getHierarchyUnitRoles(HierarchyUnit hierarchyUnit
, String filter
, boolean deep
) {
417 LdapName dn
= LdapNameUtils
.toLdapName(hierarchyUnit
.getContext());
419 return getRoles(dn
, filter
, deep
);
420 } catch (InvalidSyntaxException e
) {
421 throw new IllegalArgumentException("Cannot filter " + filter
+ " " + dn
, e
);
428 protected LdapEntry
newUser(LdapName name
, Attributes attrs
) {
429 // TODO support devices, applications, etc.
430 return new LdifUser(this, name
, attrs
);
433 protected LdapEntry
newGroup(LdapName name
, Attributes attrs
) {
434 return new LdifGroup(this, name
, attrs
);
439 protected UserAdmin
getExternalRoles() {
440 return externalRoles
;
443 public void setExternalRoles(UserAdmin externalRoles
) {
444 this.externalRoles
= externalRoles
;
447 // public void setTransactionManager(TransactionManager transactionManager) {
448 // this.transactionManager = transactionManager;
454 static LdapName
toLdapName(String name
) {
456 return new LdapName(name
);
457 } catch (InvalidNameException e
) {
458 throw new IllegalArgumentException(name
+ " is not an LDAP name", e
);