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
.util
.ArrayList
;
12 import java
.util
.Dictionary
;
13 import java
.util
.Hashtable
;
14 import java
.util
.Iterator
;
15 import java
.util
.List
;
16 import java
.util
.Optional
;
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
;
52 public DirectoryUserAdmin(URI uriArg
, Dictionary
<String
, ?
> props
) {
53 this(uriArg
, props
, false);
56 public DirectoryUserAdmin(URI uriArg
, Dictionary
<String
, ?
> props
, boolean scoped
) {
57 super(uriArg
, props
, scoped
);
60 public DirectoryUserAdmin(Dictionary
<String
, ?
> props
) {
68 protected Optional
<DirectoryUserAdmin
> scope(User user
) {
69 if (getDirectoryDao() instanceof LdapDao
) {
70 return scopeLdap(user
);
71 } else if (getDirectoryDao() instanceof LdifDao
) {
72 return scopeLdif(user
);
74 throw new IllegalStateException("Unsupported DAO " + getDirectoryDao().getClass());
78 protected Optional
<DirectoryUserAdmin
> scopeLdap(User user
) {
79 Dictionary
<String
, Object
> credentials
= user
.getCredentials();
80 String username
= (String
) credentials
.get(SHARED_STATE_USERNAME
);
82 username
= user
.getName();
83 Dictionary
<String
, Object
> properties
= cloneConfigProperties();
84 properties
.put(Context
.SECURITY_PRINCIPAL
, username
.toString());
85 Object pwdCred
= credentials
.get(SHARED_STATE_PASSWORD
);
86 byte[] pwd
= (byte[]) pwdCred
;
88 char[] password
= DirectoryDigestUtils
.bytesToChars(pwd
);
89 properties
.put(Context
.SECURITY_CREDENTIALS
, new String(password
));
91 properties
.put(Context
.SECURITY_AUTHENTICATION
, "GSSAPI");
93 DirectoryUserAdmin scopedDirectory
= new DirectoryUserAdmin(null, properties
, true);
94 scopedDirectory
.init();
96 if (!scopedDirectory
.getDirectoryDao().checkConnection())
97 return Optional
.empty();
98 return Optional
.of(scopedDirectory
);
101 protected Optional
<DirectoryUserAdmin
> scopeLdif(User user
) {
102 Dictionary
<String
, Object
> credentials
= user
.getCredentials();
103 String username
= (String
) credentials
.get(SHARED_STATE_USERNAME
);
104 if (username
== null)
105 username
= user
.getName();
106 Object pwdCred
= credentials
.get(SHARED_STATE_PASSWORD
);
107 byte[] pwd
= (byte[]) pwdCred
;
109 char[] password
= DirectoryDigestUtils
.bytesToChars(pwd
);
110 User directoryUser
= (User
) getRole(username
);
111 if (!directoryUser
.hasCredential(null, password
))
112 throw new IllegalStateException("Invalid credentials");
114 throw new IllegalStateException("Password is required");
116 Dictionary
<String
, Object
> properties
= cloneConfigProperties();
117 properties
.put(DirectoryConf
.readOnly
.name(), "true");
118 DirectoryUserAdmin scopedUserAdmin
= new DirectoryUserAdmin(null, properties
, true);
119 // FIXME do it better
120 ((LdifDao
) getDirectoryDao()).scope((LdifDao
) scopedUserAdmin
.getDirectoryDao());
121 // no need to check authentication
122 scopedUserAdmin
.init();
123 return Optional
.of(scopedUserAdmin
);
127 public String
getRolePath(Role role
) {
128 return nameToRelativePath(LdapNameUtils
.toLdapName(role
.getName()));
132 public String
getRoleSimpleName(Role role
) {
133 LdapName dn
= LdapNameUtils
.toLdapName(role
.getName());
134 String name
= LdapNameUtils
.getLastRdnValue(dn
);
139 public Role
getRoleByPath(String path
) {
140 LdapEntry entry
= doGetRole(pathToName(path
));
141 if (!(entry
instanceof Role
)) {
143 // throw new IllegalStateException("Path must be a UserAdmin Role.");
149 protected List
<Role
> getAllRoles(DirectoryUser user
) {
150 List
<Role
> allRoles
= new ArrayList
<Role
>();
152 collectRoles((LdapEntry
) user
, allRoles
);
155 collectAnonymousRoles(allRoles
);
159 private void collectRoles(LdapEntry user
, List
<Role
> allRoles
) {
160 List
<LdapEntry
> allEntries
= new ArrayList
<>();
161 LdapEntry entry
= user
;
162 collectGroups(entry
, allEntries
);
163 for (LdapEntry e
: allEntries
) {
164 if (e
instanceof Role
)
165 allRoles
.add((Role
) e
);
169 private void collectAnonymousRoles(List
<Role
> allRoles
) {
170 // TODO gather anonymous roles
175 public Role
getRole(String name
) {
176 return (Role
) doGetRole(toLdapName(name
));
180 public Role
[] getRoles(String filter
) throws InvalidSyntaxException
{
181 List
<?
extends Role
> res
= getRoles(getBaseDn(), filter
, true);
182 return res
.toArray(new Role
[res
.size()]);
185 List
<DirectoryUser
> getRoles(LdapName searchBase
, String filter
, boolean deep
) throws InvalidSyntaxException
{
186 LdapEntryWorkingCopy wc
= getWorkingCopy();
187 // Filter f = filter != null ? FrameworkUtil.createFilter(filter) : null;
188 List
<LdapEntry
> searchRes
= getDirectoryDao().doGetEntries(searchBase
, filter
, deep
);
189 List
<DirectoryUser
> res
= new ArrayList
<>();
190 for (LdapEntry entry
: searchRes
)
191 res
.add((DirectoryUser
) entry
);
193 for (Iterator
<DirectoryUser
> it
= res
.iterator(); it
.hasNext();) {
194 DirectoryUser user
= (DirectoryUser
) it
.next();
195 LdapName dn
= LdapNameUtils
.toLdapName(user
.getName());
196 if (wc
.getDeletedData().containsKey(dn
))
199 Filter f
= filter
!= null ? FrameworkUtil
.createFilter(filter
) : null;
200 for (LdapEntry ldapEntry
: wc
.getNewData().values()) {
201 DirectoryUser user
= (DirectoryUser
) ldapEntry
;
202 if (f
== null || f
.match(user
.getProperties()))
205 // no need to check modified users,
206 // since doGetRoles was already based on the modified attributes
212 public User
getUser(String key
, String value
) {
213 // TODO check value null or empty
214 List
<DirectoryUser
> collectedUsers
= new ArrayList
<DirectoryUser
>();
216 doGetUser(key
, value
, collectedUsers
);
218 throw new IllegalArgumentException("Key cannot be null");
221 if (collectedUsers
.size() == 1) {
222 return collectedUsers
.get(0);
223 } else if (collectedUsers
.size() > 1) {
224 // log.warn(collectedUsers.size() + " users for " + (key != null ? key + "=" :
230 protected void doGetUser(String key
, String value
, List
<DirectoryUser
> collectedUsers
) {
231 String f
= "(" + key
+ "=" + value
+ ")";
232 List
<LdapEntry
> users
= getDirectoryDao().doGetEntries(getBaseDn(), f
, true);
233 for (LdapEntry entry
: users
)
234 collectedUsers
.add((DirectoryUser
) entry
);
238 public Authorization
getAuthorization(User user
) {
239 if (user
== null) {// anonymous
240 return new LdifAuthorization(user
, getAllRoles(null));
242 LdapName userName
= toLdapName(user
.getName());
243 if (isExternal(userName
) && user
instanceof LdapEntry
) {
244 List
<Role
> allRoles
= new ArrayList
<Role
>();
245 collectRoles((LdapEntry
) user
, allRoles
);
246 return new LdifAuthorization(user
, allRoles
);
249 Subject currentSubject
= CurrentSubject
.current();
250 if (currentSubject
!= null //
251 && getRealm().isPresent() //
252 && !currentSubject
.getPrivateCredentials(Authorization
.class).isEmpty() //
253 && !currentSubject
.getPrivateCredentials(KerberosTicket
.class).isEmpty()) //
255 // TODO not only Kerberos but also bind scope with kept password ?
256 Authorization auth
= currentSubject
.getPrivateCredentials(Authorization
.class).iterator().next();
257 // bind with authenticating user
258 DirectoryUserAdmin scopedUserAdmin
= CurrentSubject
.callAs(currentSubject
, () -> {
259 return scope(new AuthenticatingUser(auth
.getName(), new Hashtable
<>())).orElseThrow();
261 return getAuthorizationFromScoped(scopedUserAdmin
, user
);
264 if (user
instanceof DirectoryUser
) {
265 return new LdifAuthorization(user
, getAllRoles((DirectoryUser
) user
));
267 // bind with authenticating user
268 DirectoryUserAdmin scopedUserAdmin
= scope(user
).orElseThrow();
269 return getAuthorizationFromScoped(scopedUserAdmin
, user
);
274 private Authorization
getAuthorizationFromScoped(DirectoryUserAdmin scopedUserAdmin
, User user
) {
276 DirectoryUser directoryUser
= (DirectoryUser
) scopedUserAdmin
.getRole(user
.getName());
277 if (directoryUser
== null)
278 throw new IllegalStateException("No scoped user found for " + user
);
279 LdifAuthorization authorization
= new LdifAuthorization(directoryUser
,
280 scopedUserAdmin
.getAllRoles(directoryUser
));
281 return authorization
;
283 scopedUserAdmin
.destroy();
288 public Role
createRole(String name
, int type
) {
290 LdapEntryWorkingCopy wc
= getWorkingCopy();
291 LdapName dn
= toLdapName(name
);
292 if ((getDirectoryDao().entryExists(dn
) && !wc
.getDeletedData().containsKey(dn
))
293 || wc
.getNewData().containsKey(dn
))
294 throw new IllegalArgumentException("Already a role " + name
);
295 BasicAttributes attrs
= new BasicAttributes(true);
296 // attrs.put(LdifName.dn.name(), dn.toString());
297 Rdn nameRdn
= dn
.getRdn(dn
.size() - 1);
298 // TODO deal with multiple attr RDN
299 attrs
.put(nameRdn
.getType(), nameRdn
.getValue());
300 if (wc
.getDeletedData().containsKey(dn
)) {
301 wc
.getDeletedData().remove(dn
);
302 wc
.getModifiedData().put(dn
, attrs
);
303 return getRole(name
);
305 wc
.getModifiedData().put(dn
, attrs
);
306 LdapEntry newRole
= doCreateRole(dn
, type
, attrs
);
307 wc
.getNewData().put(dn
, newRole
);
308 return (Role
) newRole
;
312 private LdapEntry
doCreateRole(LdapName dn
, int type
, Attributes attrs
) {
314 BasicAttribute objClass
= new BasicAttribute(objectClass
.name());
315 if (type
== Role
.USER
) {
316 String userObjClass
= getUserObjectClass();
317 objClass
.add(userObjClass
);
318 if (inetOrgPerson
.name().equals(userObjClass
)) {
319 objClass
.add(organizationalPerson
.name());
320 objClass
.add(person
.name());
321 } else if (organizationalPerson
.name().equals(userObjClass
)) {
322 objClass
.add(person
.name());
324 objClass
.add(top
.name());
325 objClass
.add(extensibleObject
.name());
327 newRole
= newUser(dn
);
328 } else if (type
== Role
.GROUP
) {
329 String groupObjClass
= getGroupObjectClass();
330 objClass
.add(groupObjClass
);
331 // objClass.add(LdifName.extensibleObject.name());
332 objClass
.add(top
.name());
334 newRole
= newGroup(dn
);
336 throw new IllegalArgumentException("Unsupported type " + type
);
341 public boolean removeRole(String name
) {
342 return removeEntry(LdapNameUtils
.toLdapName(name
));
349 public HierarchyUnit
getHierarchyUnit(Role role
) {
350 LdapName dn
= LdapNameUtils
.toLdapName(role
.getName());
351 LdapName huDn
= LdapNameUtils
.getParent(dn
);
352 HierarchyUnit hierarchyUnit
= getDirectoryDao().doGetHierarchyUnit(huDn
);
353 if (hierarchyUnit
== null)
354 throw new IllegalStateException("No hierarchy unit found for " + role
);
355 return hierarchyUnit
;
359 public Iterable
<?
extends Role
> getHierarchyUnitRoles(HierarchyUnit hierarchyUnit
, String filter
, boolean deep
) {
360 LdapName dn
= LdapNameUtils
.toLdapName(hierarchyUnit
.getBase());
362 return getRoles(dn
, filter
, deep
);
363 } catch (InvalidSyntaxException e
) {
364 throw new IllegalArgumentException("Cannot filter " + filter
+ " " + dn
, e
);
371 protected LdapEntry
newUser(LdapName name
) {
372 // TODO support devices, applications, etc.
373 return new LdifUser(this, name
);
376 protected LdapEntry
newGroup(LdapName name
) {
377 return new LdifGroup(this, name
);
382 protected UserAdmin
getExternalRoles() {
383 return externalRoles
;
386 public void setExternalRoles(UserAdmin externalRoles
) {
387 this.externalRoles
= externalRoles
;
393 static LdapName
toLdapName(String name
) {
395 return new LdapName(name
);
396 } catch (InvalidNameException e
) {
397 throw new IllegalArgumentException(name
+ " is not an LDAP name", e
);