1 package org
.argeo
.cms
.osgi
.useradmin
;
3 import static org
.argeo
.api
.acr
.ldap
.LdapAttr
.objectClass
;
4 import static org
.argeo
.api
.acr
.ldap
.LdapObj
.extensibleObject
;
5 import static org
.argeo
.api
.acr
.ldap
.LdapObj
.inetOrgPerson
;
6 import static org
.argeo
.api
.acr
.ldap
.LdapObj
.organizationalPerson
;
7 import static org
.argeo
.api
.acr
.ldap
.LdapObj
.person
;
8 import static org
.argeo
.api
.acr
.ldap
.LdapObj
.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
.api
.cms
.directory
.DirectoryDigestUtils
;
29 import org
.argeo
.api
.cms
.directory
.CmsUser
;
30 import org
.argeo
.api
.cms
.directory
.HierarchyUnit
;
31 import org
.argeo
.api
.cms
.directory
.UserDirectory
;
32 import org
.argeo
.cms
.directory
.ldap
.AbstractLdapDirectory
;
33 import org
.argeo
.cms
.directory
.ldap
.LdapDao
;
34 import org
.argeo
.cms
.directory
.ldap
.LdapEntry
;
35 import org
.argeo
.cms
.directory
.ldap
.LdapEntryWorkingCopy
;
36 import org
.argeo
.cms
.directory
.ldap
.LdapNameUtils
;
37 import org
.argeo
.cms
.directory
.ldap
.LdifDao
;
38 import org
.argeo
.cms
.runtime
.DirectoryConf
;
39 import org
.argeo
.cms
.util
.CurrentSubject
;
40 import org
.osgi
.framework
.Filter
;
41 import org
.osgi
.framework
.FrameworkUtil
;
42 import org
.osgi
.framework
.InvalidSyntaxException
;
43 import org
.osgi
.service
.useradmin
.Authorization
;
44 import org
.osgi
.service
.useradmin
.Role
;
45 import org
.osgi
.service
.useradmin
.User
;
46 import org
.osgi
.service
.useradmin
.UserAdmin
;
48 /** Base class for a {@link UserDirectory}. */
49 public class DirectoryUserAdmin
extends AbstractLdapDirectory
implements UserAdmin
, UserDirectory
{
51 private UserAdmin externalRoles
;
54 public DirectoryUserAdmin(URI uriArg
, Dictionary
<String
, ?
> props
) {
55 this(uriArg
, props
, false);
58 public DirectoryUserAdmin(URI uriArg
, Dictionary
<String
, ?
> props
, boolean scoped
) {
59 super(uriArg
, props
, scoped
);
62 public DirectoryUserAdmin(Dictionary
<String
, ?
> props
) {
70 protected Optional
<DirectoryUserAdmin
> scope(User user
) {
71 if (getDirectoryDao() instanceof LdapDao
) {
72 return scopeLdap(user
);
73 } else if (getDirectoryDao() instanceof LdifDao
) {
74 return scopeLdif(user
);
76 throw new IllegalStateException("Unsupported DAO " + getDirectoryDao().getClass());
80 protected Optional
<DirectoryUserAdmin
> scopeLdap(User user
) {
81 Dictionary
<String
, Object
> credentials
= user
.getCredentials();
82 String username
= (String
) credentials
.get(SHARED_STATE_USERNAME
);
84 username
= user
.getName();
85 Dictionary
<String
, Object
> properties
= cloneConfigProperties();
86 properties
.put(Context
.SECURITY_PRINCIPAL
, username
.toString());
87 Object pwdCred
= credentials
.get(SHARED_STATE_PASSWORD
);
88 byte[] pwd
= (byte[]) pwdCred
;
90 char[] password
= DirectoryDigestUtils
.bytesToChars(pwd
);
91 properties
.put(Context
.SECURITY_CREDENTIALS
, new String(password
));
93 properties
.put(Context
.SECURITY_AUTHENTICATION
, "GSSAPI");
95 DirectoryUserAdmin scopedDirectory
= new DirectoryUserAdmin(null, properties
, true);
96 scopedDirectory
.init();
98 if (!scopedDirectory
.getDirectoryDao().checkConnection())
99 return Optional
.empty();
100 return Optional
.of(scopedDirectory
);
103 protected Optional
<DirectoryUserAdmin
> scopeLdif(User user
) {
104 Dictionary
<String
, Object
> credentials
= user
.getCredentials();
105 String username
= (String
) credentials
.get(SHARED_STATE_USERNAME
);
106 if (username
== null)
107 username
= user
.getName();
108 Object pwdCred
= credentials
.get(SHARED_STATE_PASSWORD
);
109 byte[] pwd
= (byte[]) pwdCred
;
111 char[] password
= DirectoryDigestUtils
.bytesToChars(pwd
);
112 User directoryUser
= (User
) getRole(username
);
113 if (!directoryUser
.hasCredential(null, password
))
114 throw new IllegalStateException("Invalid credentials");
116 throw new IllegalStateException("Password is required");
118 Dictionary
<String
, Object
> properties
= cloneConfigProperties();
119 properties
.put(DirectoryConf
.readOnly
.name(), "true");
120 DirectoryUserAdmin scopedUserAdmin
= new DirectoryUserAdmin(null, properties
, true);
121 // FIXME do it better
122 ((LdifDao
) getDirectoryDao()).scope((LdifDao
) scopedUserAdmin
.getDirectoryDao());
123 // no need to check authentication
124 scopedUserAdmin
.init();
125 return Optional
.of(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(CmsUser 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
);
171 private void collectAnonymousRoles(List
<Role
> allRoles
) {
172 // TODO gather anonymous roles
177 public Role
getRole(String name
) {
178 return (Role
) doGetRole(toLdapName(name
));
182 public Role
[] getRoles(String filter
) throws InvalidSyntaxException
{
183 List
<?
extends Role
> res
= getRoles(getBaseDn(), filter
, true);
184 return res
.toArray(new Role
[res
.size()]);
187 List
<CmsUser
> getRoles(LdapName searchBase
, String filter
, boolean deep
) throws InvalidSyntaxException
{
188 LdapEntryWorkingCopy wc
= getWorkingCopy();
189 // Filter f = filter != null ? FrameworkUtil.createFilter(filter) : null;
190 List
<LdapEntry
> searchRes
= getDirectoryDao().doGetEntries(searchBase
, filter
, deep
);
191 List
<CmsUser
> res
= new ArrayList
<>();
192 for (LdapEntry entry
: searchRes
)
193 res
.add((CmsUser
) entry
);
195 for (Iterator
<CmsUser
> it
= res
.iterator(); it
.hasNext();) {
196 CmsUser user
= (CmsUser
) it
.next();
197 LdapName dn
= LdapNameUtils
.toLdapName(user
.getName());
198 if (wc
.getDeletedData().containsKey(dn
))
201 Filter f
= filter
!= null ? FrameworkUtil
.createFilter(filter
) : null;
202 for (LdapEntry ldapEntry
: wc
.getNewData().values()) {
203 CmsUser user
= (CmsUser
) ldapEntry
;
204 if (f
== null || f
.match(user
.getProperties()))
207 // no need to check modified users,
208 // since doGetRoles was already based on the modified attributes
214 public User
getUser(String key
, String value
) {
215 // TODO check value null or empty
216 List
<CmsUser
> collectedUsers
= new ArrayList
<CmsUser
>();
218 doGetUser(key
, value
, collectedUsers
);
220 throw new IllegalArgumentException("Key cannot be null");
223 if (collectedUsers
.size() == 1) {
224 return collectedUsers
.get(0);
225 } else if (collectedUsers
.size() > 1) {
226 // log.warn(collectedUsers.size() + " users for " + (key != null ? key + "=" :
232 protected void doGetUser(String key
, String value
, List
<CmsUser
> collectedUsers
) {
233 String f
= "(" + key
+ "=" + value
+ ")";
234 List
<LdapEntry
> users
= getDirectoryDao().doGetEntries(getBaseDn(), f
, true);
235 for (LdapEntry entry
: users
)
236 collectedUsers
.add((CmsUser
) entry
);
240 public Authorization
getAuthorization(User user
) {
241 if (user
== null) {// anonymous
242 return new LdifAuthorization(user
, getAllRoles(null));
244 LdapName userName
= toLdapName(user
.getName());
245 if (isExternal(userName
) && user
instanceof LdapEntry
) {
246 List
<Role
> allRoles
= new ArrayList
<Role
>();
247 collectRoles((LdapEntry
) user
, allRoles
);
248 return new LdifAuthorization(user
, allRoles
);
251 Subject currentSubject
= CurrentSubject
.current();
252 if (currentSubject
!= null //
253 && getRealm().isPresent() //
254 && !currentSubject
.getPrivateCredentials(Authorization
.class).isEmpty() //
255 && !currentSubject
.getPrivateCredentials(KerberosTicket
.class).isEmpty()) //
257 // TODO not only Kerberos but also bind scope with kept password ?
258 Authorization auth
= currentSubject
.getPrivateCredentials(Authorization
.class).iterator().next();
259 // bind with authenticating user
260 DirectoryUserAdmin scopedUserAdmin
= CurrentSubject
.callAs(currentSubject
, () -> {
261 return scope(new AuthenticatingUser(auth
.getName(), new Hashtable
<>())).orElseThrow();
263 return getAuthorizationFromScoped(scopedUserAdmin
, user
);
266 if (user
instanceof CmsUser
) {
267 return new LdifAuthorization(user
, getAllRoles((CmsUser
) user
));
269 // bind with authenticating user
270 DirectoryUserAdmin scopedUserAdmin
= scope(user
).orElseThrow();
271 return getAuthorizationFromScoped(scopedUserAdmin
, user
);
276 private Authorization
getAuthorizationFromScoped(DirectoryUserAdmin scopedUserAdmin
, User user
) {
278 CmsUser directoryUser
= (CmsUser
) scopedUserAdmin
.getRole(user
.getName());
279 if (directoryUser
== null)
280 throw new IllegalStateException("No scoped user found for " + user
);
281 LdifAuthorization authorization
= new LdifAuthorization(directoryUser
,
282 scopedUserAdmin
.getAllRoles(directoryUser
));
283 return authorization
;
285 scopedUserAdmin
.destroy();
290 public Role
createRole(String name
, int type
) {
292 LdapEntryWorkingCopy wc
= getWorkingCopy();
293 LdapName dn
= toLdapName(name
);
294 if ((getDirectoryDao().entryExists(dn
) && !wc
.getDeletedData().containsKey(dn
))
295 || wc
.getNewData().containsKey(dn
))
296 throw new IllegalArgumentException("Already a role " + name
);
297 BasicAttributes attrs
= new BasicAttributes(true);
298 // attrs.put(LdifName.dn.name(), dn.toString());
299 Rdn nameRdn
= dn
.getRdn(dn
.size() - 1);
300 // TODO deal with multiple attr RDN
301 attrs
.put(nameRdn
.getType(), nameRdn
.getValue());
302 if (wc
.getDeletedData().containsKey(dn
)) {
303 wc
.getDeletedData().remove(dn
);
304 wc
.getModifiedData().put(dn
, attrs
);
305 return getRole(name
);
307 wc
.getModifiedData().put(dn
, attrs
);
308 LdapEntry newRole
= doCreateRole(dn
, type
, attrs
);
309 wc
.getNewData().put(dn
, newRole
);
310 return (Role
) newRole
;
314 private LdapEntry
doCreateRole(LdapName dn
, int type
, Attributes attrs
) {
316 BasicAttribute objClass
= new BasicAttribute(objectClass
.name());
317 if (type
== Role
.USER
) {
318 String userObjClass
= getUserObjectClass();
319 objClass
.add(userObjClass
);
320 if (inetOrgPerson
.name().equals(userObjClass
)) {
321 objClass
.add(organizationalPerson
.name());
322 objClass
.add(person
.name());
323 } else if (organizationalPerson
.name().equals(userObjClass
)) {
324 objClass
.add(person
.name());
326 objClass
.add(top
.name());
327 objClass
.add(extensibleObject
.name());
329 newRole
= newUser(dn
);
330 } else if (type
== Role
.GROUP
) {
331 String groupObjClass
= getGroupObjectClass();
332 objClass
.add(groupObjClass
);
333 // objClass.add(LdifName.extensibleObject.name());
334 objClass
.add(top
.name());
336 newRole
= newGroup(dn
);
338 throw new IllegalArgumentException("Unsupported type " + type
);
343 public boolean removeRole(String name
) {
344 return removeEntry(LdapNameUtils
.toLdapName(name
));
351 public HierarchyUnit
getHierarchyUnit(Role role
) {
352 LdapName dn
= LdapNameUtils
.toLdapName(role
.getName());
353 LdapName huDn
= LdapNameUtils
.getParent(dn
);
354 HierarchyUnit hierarchyUnit
= getDirectoryDao().doGetHierarchyUnit(huDn
);
355 if (hierarchyUnit
== null)
356 throw new IllegalStateException("No hierarchy unit found for " + role
);
357 return hierarchyUnit
;
361 public Iterable
<?
extends Role
> getHierarchyUnitRoles(HierarchyUnit hierarchyUnit
, String filter
, boolean deep
) {
362 LdapName dn
= LdapNameUtils
.toLdapName(hierarchyUnit
.getBase());
364 return getRoles(dn
, filter
, deep
);
365 } catch (InvalidSyntaxException e
) {
366 throw new IllegalArgumentException("Cannot filter " + filter
+ " " + dn
, e
);
373 protected LdapEntry
newUser(LdapName name
) {
374 // TODO support devices, applications, etc.
375 return new LdifUser(this, name
);
378 protected LdapEntry
newGroup(LdapName name
) {
379 return new LdifGroup(this, name
);
384 protected UserAdmin
getExternalRoles() {
385 return externalRoles
;
388 public void setExternalRoles(UserAdmin externalRoles
) {
389 this.externalRoles
= externalRoles
;
395 static LdapName
toLdapName(String name
) {
397 return new LdapName(name
);
398 } catch (InvalidNameException e
) {
399 throw new IllegalArgumentException(name
+ " is not an LDAP name", e
);