1 package org
.argeo
.util
.directory
.ldap
;
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
.HashSet
;
16 import java
.util
.List
;
17 import java
.util
.NavigableMap
;
18 import java
.util
.Objects
;
20 import java
.util
.SortedMap
;
21 import java
.util
.TreeMap
;
23 import javax
.naming
.NameNotFoundException
;
24 import javax
.naming
.NamingEnumeration
;
25 import javax
.naming
.NamingException
;
26 import javax
.naming
.directory
.Attributes
;
27 import javax
.naming
.ldap
.LdapName
;
29 import org
.argeo
.util
.directory
.HierarchyUnit
;
30 import org
.argeo
.util
.naming
.LdapObjs
;
31 import org
.osgi
.framework
.Filter
;
32 import org
.osgi
.framework
.FrameworkUtil
;
33 import org
.osgi
.framework
.InvalidSyntaxException
;
34 import org
.osgi
.service
.useradmin
.Role
;
36 /** A user admin based on a LDIF files. */
37 public class LdifDao
extends AbstractLdapDirectoryDao
{
38 private NavigableMap
<LdapName
, LdapEntry
> entries
= new TreeMap
<>();
39 private NavigableMap
<LdapName
, LdapHierarchyUnit
> hierarchy
= new TreeMap
<>();
41 private NavigableMap
<LdapName
, Attributes
> values
= new TreeMap
<>();
43 public LdifDao(AbstractLdapDirectory directory
) {
48 String uri
= getDirectory().getUri();
53 if (u
.getScheme().equals("file")) {
54 File file
= new File(u
);
58 load(u
.toURL().openStream());
59 } catch (IOException
| URISyntaxException e
) {
60 throw new IllegalStateException("Cannot open URL " + getDirectory().getUri(), e
);
65 if (getDirectory().getUri() == null)
67 if (getDirectory().isReadOnly())
68 throw new IllegalStateException(
69 "Cannot save LDIF user admin: " + getDirectory().getUri() + " is read-only");
70 try (FileOutputStream out
= new FileOutputStream(new File(new URI(getDirectory().getUri())))) {
72 } catch (IOException
| URISyntaxException e
) {
73 throw new IllegalStateException("Cannot save user admin to " + getDirectory().getUri(), e
);
77 public void save(OutputStream out
) throws IOException
{
79 LdifWriter ldifWriter
= new LdifWriter(out
);
80 for (LdapName name
: hierarchy
.keySet())
81 ldifWriter
.writeEntry(name
, hierarchy
.get(name
).getAttributes());
82 for (LdapName name
: entries
.keySet())
83 ldifWriter
.writeEntry(name
, entries
.get(name
).getAttributes());
89 public void load(InputStream in
) {
94 LdifParser ldifParser
= new LdifParser();
95 SortedMap
<LdapName
, Attributes
> allEntries
= ldifParser
.read(in
);
96 for (LdapName key
: allEntries
.keySet()) {
97 Attributes attributes
= allEntries
.get(key
);
98 // check for inconsistency
99 Set
<String
> lowerCase
= new HashSet
<String
>();
100 NamingEnumeration
<String
> ids
= attributes
.getIDs();
101 while (ids
.hasMoreElements()) {
102 String id
= ids
.nextElement().toLowerCase();
103 if (lowerCase
.contains(id
))
104 throw new IllegalStateException(key
+ " has duplicate id " + id
);
108 values
.put(key
, attributes
);
110 // analyse object classes
111 NamingEnumeration
<?
> objectClasses
= attributes
.get(objectClass
.name()).getAll();
112 // System.out.println(key);
113 objectClasses
: while (objectClasses
.hasMore()) {
114 String objectClass
= objectClasses
.next().toString();
115 // System.out.println(" " + objectClass);
116 if (objectClass
.toLowerCase().equals(inetOrgPerson
.name().toLowerCase())) {
117 entries
.put(key
, newUser(key
));
119 } else if (objectClass
.toLowerCase().equals(getDirectory().getGroupObjectClass().toLowerCase())) {
120 entries
.put(key
, newGroup(key
));
122 } else if (objectClass
.equalsIgnoreCase(LdapObjs
.organizationalUnit
.name())) {
123 // TODO skip if it does not contain groups or users
124 hierarchy
.put(key
, new LdapHierarchyUnit(getDirectory(), key
));
130 } catch (NamingException
| IOException e
) {
131 throw new IllegalStateException("Cannot load user admin service from LDIF", e
);
135 public void destroy() {
136 // if (users == null || groups == null)
138 throw new IllegalStateException("User directory " + getDirectory().getBaseDn() + " is already destroyed");
149 public LdapEntry
doGetEntry(LdapName key
) throws NameNotFoundException
{
150 if (entries
.containsKey(key
))
151 return entries
.get(key
);
152 throw new NameNotFoundException(key
+ " not persisted");
156 public Attributes
doGetAttributes(LdapName name
) {
157 if (!values
.containsKey(name
))
158 throw new IllegalStateException(name
+ " doe not exist in " + getDirectory().getBaseDn());
159 return values
.get(name
);
163 public boolean checkConnection() {
168 public boolean entryExists(LdapName dn
) {
169 return entries
.containsKey(dn
);// || groups.containsKey(dn);
173 public List
<LdapEntry
> doGetEntries(LdapName searchBase
, String f
, boolean deep
) {
174 Objects
.requireNonNull(searchBase
);
175 ArrayList
<LdapEntry
> res
= new ArrayList
<>();
176 if (f
== null && deep
&& getDirectory().getBaseDn().equals(searchBase
)) {
177 res
.addAll(entries
.values());
179 filterRoles(entries
, searchBase
, f
, deep
, res
);
184 private void filterRoles(SortedMap
<LdapName
, ?
extends LdapEntry
> map
, LdapName searchBase
, String f
, boolean deep
,
185 List
<LdapEntry
> res
) {
186 // FIXME get rid of OSGi references
188 // TODO reduce map with search base ?
189 Filter filter
= f
!= null ? FrameworkUtil
.createFilter(f
) : null;
190 roles
: for (LdapEntry user
: map
.values()) {
191 LdapName dn
= user
.getDn();
192 if (dn
.startsWith(searchBase
)) {
193 if (!deep
&& dn
.size() != (searchBase
.size() + 1))
198 if (user
instanceof Role
) {
199 if (filter
.match(((Role
) user
).getProperties()))
205 } catch (InvalidSyntaxException e
) {
206 throw new IllegalArgumentException("Cannot create filter " + f
, e
);
212 public List
<LdapName
> getDirectGroups(LdapName dn
) {
213 List
<LdapName
> directGroups
= new ArrayList
<LdapName
>();
214 entries
: for (LdapName name
: entries
.keySet()) {
217 LdapEntry entry
= doGetEntry(name
);
218 if (AbstractLdapDirectory
.hasObjectClass(entry
.getAttributes(), getDirectory().getGroupObjectClass())) {
223 } catch (NameNotFoundException e
) {
224 throw new IllegalArgumentException("Group " + dn
+ " not found", e
);
226 if (group
.getReferences(getDirectory().getMemberAttributeId()).contains(dn
)) {
227 directGroups
.add(group
.getDn());
234 public void prepare(LdapEntryWorkingCopy wc
) {
236 for (LdapName dn
: wc
.getDeletedData().keySet()) {
237 if (entries
.containsKey(dn
))
240 throw new IllegalStateException("User to delete not found " + dn
);
243 for (LdapName dn
: wc
.getNewData().keySet()) {
244 LdapEntry user
= (LdapEntry
) wc
.getNewData().get(dn
);
245 if (entries
.containsKey(dn
))
246 throw new IllegalStateException("User to create found " + dn
);
247 entries
.put(dn
, user
);
250 for (LdapName dn
: wc
.getModifiedData().keySet()) {
251 Attributes modifiedAttrs
= wc
.getModifiedData().get(dn
);
254 user
= doGetEntry(dn
);
255 } catch (NameNotFoundException e
) {
256 throw new IllegalStateException("User to modify no found " + dn
, e
);
259 throw new IllegalStateException("User to modify no found " + dn
);
260 user
.publishAttributes(modifiedAttrs
);
265 public void commit(LdapEntryWorkingCopy wc
) {
270 public void rollback(LdapEntryWorkingCopy wc
) {
278 public HierarchyUnit
doGetHierarchyUnit(LdapName dn
) {
279 if (getDirectory().getBaseDn().equals(dn
))
280 return getDirectory();
281 return hierarchy
.get(dn
);
285 public Iterable
<HierarchyUnit
> doGetDirectHierarchyUnits(LdapName searchBase
, boolean functionalOnly
) {
286 List
<HierarchyUnit
> res
= new ArrayList
<>();
287 for (LdapName n
: hierarchy
.keySet()) {
288 if (n
.size() == searchBase
.size() + 1) {
289 if (n
.startsWith(searchBase
)) {
290 HierarchyUnit hu
= hierarchy
.get(n
);
291 if (functionalOnly
) {
292 if (hu
.isFunctional())
303 public void scope(LdifDao scoped
) {
304 scoped
.entries
= Collections
.unmodifiableNavigableMap(entries
);