1 package org
.argeo
.osgi
.useradmin
;
3 import static org
.argeo
.util
.naming
.LdapAttrs
.objectClass
;
4 import static org
.argeo
.util
.naming
.LdapObjs
.inetOrgPerson
;
7 import java
.io
.FileOutputStream
;
8 import java
.io
.IOException
;
9 import java
.io
.InputStream
;
10 import java
.io
.OutputStream
;
12 import java
.net
.URISyntaxException
;
13 import java
.util
.ArrayList
;
14 import java
.util
.Collections
;
15 import java
.util
.Dictionary
;
16 import java
.util
.HashSet
;
17 import java
.util
.Hashtable
;
18 import java
.util
.List
;
19 import java
.util
.NavigableMap
;
20 import java
.util
.Objects
;
22 import java
.util
.SortedMap
;
23 import java
.util
.TreeMap
;
25 import javax
.naming
.NameNotFoundException
;
26 import javax
.naming
.NamingEnumeration
;
27 import javax
.naming
.NamingException
;
28 import javax
.naming
.directory
.Attributes
;
29 import javax
.naming
.ldap
.LdapName
;
31 import org
.argeo
.util
.naming
.LdapObjs
;
32 import org
.argeo
.util
.naming
.ldap
.LdifParser
;
33 import org
.argeo
.util
.naming
.ldap
.LdifWriter
;
34 import org
.osgi
.framework
.Filter
;
35 import org
.osgi
.service
.useradmin
.Role
;
36 import org
.osgi
.service
.useradmin
.User
;
38 /** A user admin based on a LDIF files. */
39 public class LdifUserAdmin
extends AbstractUserDirectory
{
40 private NavigableMap
<LdapName
, DirectoryUser
> users
= new TreeMap
<>();
41 private NavigableMap
<LdapName
, DirectoryGroup
> groups
= new TreeMap
<>();
43 private NavigableMap
<LdapName
, LdifHierarchyUnit
> hierarchy
= new TreeMap
<>();
44 // private List<HierarchyUnit> rootHierarchyUnits = new ArrayList<>();
46 public LdifUserAdmin(String uri
, String baseDn
) {
47 this(fromUri(uri
, baseDn
), false);
50 public LdifUserAdmin(Dictionary
<String
, ?
> properties
) {
51 this(properties
, false);
54 protected LdifUserAdmin(Dictionary
<String
, ?
> properties
, boolean scoped
) {
55 super(null, properties
, scoped
);
58 public LdifUserAdmin(URI uri
, Dictionary
<String
, ?
> properties
) {
59 super(uri
, properties
, false);
63 protected AbstractUserDirectory
scope(User user
) {
64 Dictionary
<String
, Object
> credentials
= user
.getCredentials();
65 String username
= (String
) credentials
.get(SHARED_STATE_USERNAME
);
67 username
= user
.getName();
68 Object pwdCred
= credentials
.get(SHARED_STATE_PASSWORD
);
69 byte[] pwd
= (byte[]) pwdCred
;
71 char[] password
= DigestUtils
.bytesToChars(pwd
);
72 User directoryUser
= (User
) getRole(username
);
73 if (!directoryUser
.hasCredential(null, password
))
74 throw new IllegalStateException("Invalid credentials");
76 throw new IllegalStateException("Password is required");
78 Dictionary
<String
, Object
> properties
= cloneProperties();
79 properties
.put(UserAdminConf
.readOnly
.name(), "true");
80 LdifUserAdmin scopedUserAdmin
= new LdifUserAdmin(properties
, true);
81 scopedUserAdmin
.groups
= Collections
.unmodifiableNavigableMap(groups
);
82 scopedUserAdmin
.users
= Collections
.unmodifiableNavigableMap(users
);
83 return scopedUserAdmin
;
86 private static Dictionary
<String
, Object
> fromUri(String uri
, String baseDn
) {
87 Hashtable
<String
, Object
> res
= new Hashtable
<String
, Object
>();
88 res
.put(UserAdminConf
.uri
.name(), uri
);
89 res
.put(UserAdminConf
.baseDn
.name(), baseDn
);
96 URI u
= new URI(getUri());
97 if (u
.getScheme().equals("file")) {
98 File file
= new File(u
);
102 load(u
.toURL().openStream());
103 } catch (IOException
| URISyntaxException e
) {
104 throw new IllegalStateException("Cannot open URL " + getUri(), e
);
109 if (getUri() == null)
110 throw new IllegalStateException("Cannot save LDIF user admin: no URI is set");
112 throw new IllegalStateException("Cannot save LDIF user admin: " + getUri() + " is read-only");
113 try (FileOutputStream out
= new FileOutputStream(new File(new URI(getUri())))) {
115 } catch (IOException
| URISyntaxException e
) {
116 throw new IllegalStateException("Cannot save user admin to " + getUri(), e
);
120 public void save(OutputStream out
) throws IOException
{
122 LdifWriter ldifWriter
= new LdifWriter(out
);
123 for (LdapName name
: hierarchy
.keySet())
124 ldifWriter
.writeEntry(name
, hierarchy
.get(name
).getAttributes());
125 for (LdapName name
: groups
.keySet())
126 ldifWriter
.writeEntry(name
, groups
.get(name
).getAttributes());
127 for (LdapName name
: users
.keySet())
128 ldifWriter
.writeEntry(name
, users
.get(name
).getAttributes());
134 protected void load(InputStream in
) {
140 LdifParser ldifParser
= new LdifParser();
141 SortedMap
<LdapName
, Attributes
> allEntries
= ldifParser
.read(in
);
142 for (LdapName key
: allEntries
.keySet()) {
143 Attributes attributes
= allEntries
.get(key
);
144 // check for inconsistency
145 Set
<String
> lowerCase
= new HashSet
<String
>();
146 NamingEnumeration
<String
> ids
= attributes
.getIDs();
147 while (ids
.hasMoreElements()) {
148 String id
= ids
.nextElement().toLowerCase();
149 if (lowerCase
.contains(id
))
150 throw new IllegalStateException(key
+ " has duplicate id " + id
);
154 // analyse object classes
155 NamingEnumeration
<?
> objectClasses
= attributes
.get(objectClass
.name()).getAll();
156 // System.out.println(key);
157 objectClasses
: while (objectClasses
.hasMore()) {
158 String objectClass
= objectClasses
.next().toString();
159 // System.out.println(" " + objectClass);
160 if (objectClass
.toLowerCase().equals(inetOrgPerson
.name().toLowerCase())) {
161 users
.put(key
, newUser(key
, attributes
));
163 } else if (objectClass
.toLowerCase().equals(getGroupObjectClass().toLowerCase())) {
164 groups
.put(key
, newGroup(key
, attributes
));
166 // } else if (objectClass.equalsIgnoreCase(LdapObjs.organization.name())) {
167 // // we only consider organizations which are not groups
168 // hierarchy.put(key, new LdifHierarchyUnit(this, key, HierarchyUnit.ORGANIZATION, attributes));
169 // break objectClasses;
170 } else if (objectClass
.equalsIgnoreCase(LdapObjs
.organizationalUnit
.name())) {
171 // String name = key.getRdn(key.size() - 1).toString();
172 // if (getUserBase().equalsIgnoreCase(name) || getGroupBase().equalsIgnoreCase(name))
173 // break objectClasses; // skip
174 // TODO skip if it does not contain groups or users
175 hierarchy
.put(key
, new LdifHierarchyUnit(this, key
, attributes
));
182 // hierachyUnits: for (LdapName dn : hierarchy.keySet()) {
183 // LdifHierarchyUnit unit = hierarchy.get(dn);
184 // LdapName parentDn = (LdapName) dn.getPrefix(dn.size() - 1);
185 // LdifHierarchyUnit parent = hierarchy.get(parentDn);
186 // if (parent == null) {
187 // rootHierarchyUnits.add(unit);
188 // unit.parent = null;
189 // continue hierachyUnits;
191 // parent.children.add(unit);
192 // unit.parent = parent;
194 } catch (NamingException
| IOException e
) {
195 throw new IllegalStateException("Cannot load user admin service from LDIF", e
);
199 public void destroy() {
200 if (users
== null || groups
== null)
201 throw new IllegalStateException("User directory " + getBaseDn() + " is already destroyed");
211 protected DirectoryUser
daoGetRole(LdapName key
) throws NameNotFoundException
{
212 if (groups
.containsKey(key
))
213 return groups
.get(key
);
214 if (users
.containsKey(key
))
215 return users
.get(key
);
216 throw new NameNotFoundException(key
+ " not persisted");
220 protected Boolean
daoHasRole(LdapName dn
) {
221 return users
.containsKey(dn
) || groups
.containsKey(dn
);
225 protected List
<DirectoryUser
> doGetRoles(LdapName searchBase
, Filter f
, boolean deep
) {
226 Objects
.requireNonNull(searchBase
);
227 ArrayList
<DirectoryUser
> res
= new ArrayList
<DirectoryUser
>();
228 if (f
== null && deep
&& getBaseDn().equals(searchBase
)) {
229 res
.addAll(users
.values());
230 res
.addAll(groups
.values());
232 filterRoles(users
, searchBase
, f
, deep
, res
);
233 filterRoles(groups
, searchBase
, f
, deep
, res
);
238 private void filterRoles(SortedMap
<LdapName
, ?
extends DirectoryUser
> map
, LdapName searchBase
, Filter f
,
239 boolean deep
, List
<DirectoryUser
> res
) {
240 // TODO reduce map with search base ?
241 roles
: for (DirectoryUser user
: map
.values()) {
242 LdapName dn
= user
.getDn();
243 if (dn
.startsWith(searchBase
)) {
244 if (!deep
&& dn
.size() != (searchBase
.size() + 1))
248 else if (f
.match(user
.getProperties()))
256 protected List
<LdapName
> getDirectGroups(LdapName dn
) {
257 List
<LdapName
> directGroups
= new ArrayList
<LdapName
>();
258 for (LdapName name
: groups
.keySet()) {
259 DirectoryGroup group
= groups
.get(name
);
260 if (group
.getMemberNames().contains(dn
))
261 directGroups
.add(group
.getDn());
267 protected void prepare(UserDirectoryWorkingCopy wc
) {
269 for (LdapName dn
: wc
.getDeletedUsers().keySet()) {
270 if (users
.containsKey(dn
))
272 else if (groups
.containsKey(dn
))
275 throw new IllegalStateException("User to delete not found " + dn
);
278 for (LdapName dn
: wc
.getNewUsers().keySet()) {
279 DirectoryUser user
= wc
.getNewUsers().get(dn
);
280 if (users
.containsKey(dn
) || groups
.containsKey(dn
))
281 throw new IllegalStateException("User to create found " + dn
);
282 else if (Role
.USER
== user
.getType())
284 else if (Role
.GROUP
== user
.getType())
285 groups
.put(dn
, (DirectoryGroup
) user
);
287 throw new IllegalStateException("Unsupported role type " + user
.getType() + " for new user " + dn
);
290 for (LdapName dn
: wc
.getModifiedUsers().keySet()) {
291 Attributes modifiedAttrs
= wc
.getModifiedUsers().get(dn
);
293 if (users
.containsKey(dn
))
294 user
= users
.get(dn
);
295 else if (groups
.containsKey(dn
))
296 user
= groups
.get(dn
);
298 throw new IllegalStateException("User to modify no found " + dn
);
299 user
.publishAttributes(modifiedAttrs
);
304 protected void commit(UserDirectoryWorkingCopy wc
) {
309 protected void rollback(UserDirectoryWorkingCopy wc
) {
318 // public int getHierarchyChildCount() {
319 // return rootHierarchyUnits.size();
323 // public HierarchyUnit getHierarchyChild(int i) {
324 // return rootHierarchyUnits.get(i);
327 protected HierarchyUnit
doGetHierarchyUnit(LdapName dn
) {
328 return hierarchy
.get(dn
);
332 protected Iterable
<HierarchyUnit
> doGetDirectHierarchyUnits(LdapName searchBase
, boolean functionalOnly
) {
333 List
<HierarchyUnit
> res
= new ArrayList
<>();
334 for (LdapName n
: hierarchy
.keySet()) {
335 if (n
.size() == searchBase
.size() + 1) {
336 if (n
.startsWith(searchBase
)) {
337 HierarchyUnit hu
= hierarchy
.get(n
);
338 if (functionalOnly
) {
339 if (hu
.isFunctional())
351 // public Iterable<HierarchyUnit> getDirectHierarchyUnits(boolean functionalOnly) {
352 // if (functionalOnly) {
353 // List<HierarchyUnit> res = new ArrayList<>();
354 // for (HierarchyUnit hu : rootHierarchyUnits) {
355 // if (hu.isFunctional())
361 // return rootHierarchyUnits;