1 package org
.argeo
.osgi
.useradmin
;
3 import java
.nio
.ByteBuffer
;
4 import java
.nio
.CharBuffer
;
5 import java
.nio
.charset
.Charset
;
6 import java
.util
.ArrayList
;
7 import java
.util
.Arrays
;
8 import java
.util
.Collections
;
9 import java
.util
.Dictionary
;
10 import java
.util
.Enumeration
;
11 import java
.util
.Iterator
;
12 import java
.util
.List
;
14 import javax
.naming
.NamingEnumeration
;
15 import javax
.naming
.NamingException
;
16 import javax
.naming
.directory
.Attribute
;
17 import javax
.naming
.directory
.Attributes
;
18 import javax
.naming
.directory
.BasicAttribute
;
19 import javax
.naming
.ldap
.LdapName
;
21 import org
.apache
.commons
.codec
.binary
.Base64
;
22 import org
.apache
.commons
.codec
.digest
.DigestUtils
;
23 import org
.argeo
.osgi
.useradmin
.AbstractUserDirectory
.WorkingCopy
;
25 /** Directory user implementation */
26 class LdifUser
implements DirectoryUser
{
27 private final AbstractUserDirectory userAdmin
;
29 private final LdapName dn
;
31 private final boolean frozen
;
32 private Attributes publishedAttributes
;
34 private final AttributeDictionary properties
;
35 private final AttributeDictionary credentials
;
37 LdifUser(AbstractUserDirectory userAdmin
, LdapName dn
, Attributes attributes
) {
38 this(userAdmin
, dn
, attributes
, false);
41 private LdifUser(AbstractUserDirectory userAdmin
, LdapName dn
,
42 Attributes attributes
, boolean frozen
) {
43 this.userAdmin
= userAdmin
;
45 this.publishedAttributes
= attributes
;
46 properties
= new AttributeDictionary(false);
47 credentials
= new AttributeDictionary(true);
52 public String
getName() {
57 public int getType() {
62 public Dictionary
<String
, Object
> getProperties() {
67 public Dictionary
<String
, Object
> getCredentials() {
72 public boolean hasCredential(String key
, Object value
) {
74 // TODO check other sources (like PKCS12)
75 char[] password
= toChars(value
);
76 byte[] hashedPassword
= hash(password
);
77 return hasCredential(LdifName
.userpassword
.name(), hashedPassword
);
80 Object storedValue
= getCredentials().get(key
);
81 if (storedValue
== null || value
== null)
83 if (!(value
instanceof String
|| value
instanceof byte[]))
85 if (storedValue
instanceof String
&& value
instanceof String
)
86 return storedValue
.equals(value
);
87 if (storedValue
instanceof byte[] && value
instanceof byte[])
88 return Arrays
.equals((byte[]) storedValue
, (byte[]) value
);
92 /** Hash and clear the password */
93 private byte[] hash(char[] password
) {
94 byte[] hashedPassword
= ("{SHA}" + Base64
95 .encodeBase64String(DigestUtils
.sha1(toBytes(password
))))
97 Arrays
.fill(password
, '\u0000');
98 return hashedPassword
;
101 private byte[] toBytes(char[] chars
) {
102 CharBuffer charBuffer
= CharBuffer
.wrap(chars
);
103 ByteBuffer byteBuffer
= Charset
.forName("UTF-8").encode(charBuffer
);
104 byte[] bytes
= Arrays
.copyOfRange(byteBuffer
.array(),
105 byteBuffer
.position(), byteBuffer
.limit());
106 Arrays
.fill(charBuffer
.array(), '\u0000'); // clear sensitive data
107 Arrays
.fill(byteBuffer
.array(), (byte) 0); // clear sensitive data
111 private char[] toChars(Object obj
) {
112 if (obj
instanceof char[])
114 if (!(obj
instanceof byte[]))
115 throw new IllegalArgumentException(obj
.getClass()
116 + " is not a byte array");
117 ByteBuffer fromBuffer
= ByteBuffer
.wrap((byte[]) obj
);
118 CharBuffer toBuffer
= Charset
.forName("UTF-8").decode(fromBuffer
);
119 char[] res
= Arrays
.copyOfRange(toBuffer
.array(), toBuffer
.position(),
121 Arrays
.fill(fromBuffer
.array(), (byte) 0); // clear sensitive data
122 Arrays
.fill((byte[]) obj
, (byte) 0); // clear sensitive data
123 Arrays
.fill(toBuffer
.array(), '\u0000'); // clear sensitive data
128 public LdapName
getDn() {
133 public synchronized Attributes
getAttributes() {
134 return isEditing() ?
getModifiedAttributes() : publishedAttributes
;
137 /** Should only be called from working copy thread. */
138 private synchronized Attributes
getModifiedAttributes() {
139 assert getWc() != null;
140 return getWc().getAttributes(getDn());
143 protected synchronized boolean isEditing() {
144 return getWc() != null && getModifiedAttributes() != null;
147 private synchronized WorkingCopy
getWc() {
148 return userAdmin
.getWorkingCopy();
151 protected synchronized void startEditing() {
153 throw new UserDirectoryException("Cannot edit frozen view");
154 if (getUserAdmin().isReadOnly())
155 throw new UserDirectoryException("User directory is read-only");
156 assert getModifiedAttributes() == null;
157 getWc().startEditing(this);
158 // modifiedAttributes = (Attributes) publishedAttributes.clone();
161 public synchronized void publishAttributes(Attributes modifiedAttributes
) {
162 publishedAttributes
= modifiedAttributes
;
165 // protected synchronized void stopEditing(boolean apply) {
166 // assert getModifiedAttributes() != null;
168 // publishedAttributes = getModifiedAttributes();
169 // // modifiedAttributes = null;
172 public DirectoryUser
getPublished() {
173 return new LdifUser(userAdmin
, dn
, publishedAttributes
, true);
177 public int hashCode() {
178 return dn
.hashCode();
182 public boolean equals(Object obj
) {
185 if (obj
instanceof LdifUser
) {
186 LdifUser that
= (LdifUser
) obj
;
187 return this.dn
.equals(that
.dn
);
193 public String
toString() {
194 return dn
.toString();
197 protected AbstractUserDirectory
getUserAdmin() {
201 private class AttributeDictionary
extends Dictionary
<String
, Object
> {
202 private final List
<String
> effectiveKeys
= new ArrayList
<String
>();
203 private final List
<String
> attrFilter
;
204 private final Boolean includeFilter
;
206 public AttributeDictionary(Boolean includeFilter
) {
207 this.attrFilter
= userAdmin
.getCredentialAttributeIds();
208 this.includeFilter
= includeFilter
;
210 NamingEnumeration
<String
> ids
= getAttributes().getIDs();
211 while (ids
.hasMore()) {
212 String id
= ids
.next();
213 if (includeFilter
&& attrFilter
.contains(id
))
214 effectiveKeys
.add(id
);
215 else if (!includeFilter
&& !attrFilter
.contains(id
))
216 effectiveKeys
.add(id
);
218 } catch (NamingException e
) {
219 throw new UserDirectoryException(
220 "Cannot initialise attribute dictionary", e
);
226 return effectiveKeys
.size();
230 public boolean isEmpty() {
231 return effectiveKeys
.size() == 0;
235 public Enumeration
<String
> keys() {
236 return Collections
.enumeration(effectiveKeys
);
240 public Enumeration
<Object
> elements() {
241 final Iterator
<String
> it
= effectiveKeys
.iterator();
242 return new Enumeration
<Object
>() {
245 public boolean hasMoreElements() {
250 public Object
nextElement() {
251 String key
= it
.next();
253 return getAttributes().get(key
).get();
254 } catch (NamingException e
) {
255 throw new UserDirectoryException(
256 "Cannot get value for key " + key
, e
);
264 public Object
get(Object key
) {
266 Attribute attr
= getAttributes().get(key
.toString());
270 } catch (NamingException e
) {
271 throw new UserDirectoryException(
272 "Cannot get value for attribute " + key
, e
);
277 public Object
put(String key
, Object value
) {
279 // TODO persist to other sources (like PKCS12)
280 char[] password
= toChars(value
);
281 byte[] hashedPassword
= hash(password
);
282 return put(LdifName
.userpassword
.name(), hashedPassword
);
285 userAdmin
.checkEdit();
289 if (!(value
instanceof String
|| value
instanceof byte[]))
290 throw new IllegalArgumentException(
291 "Value must be String or byte[]");
293 if (includeFilter
&& !attrFilter
.contains(key
))
294 throw new IllegalArgumentException("Key " + key
296 else if (!includeFilter
&& attrFilter
.contains(key
))
297 throw new IllegalArgumentException("Key " + key
+ " excluded");
300 Attribute attribute
= getModifiedAttributes().get(
302 attribute
= new BasicAttribute(key
.toString());
303 attribute
.add(value
);
304 Attribute previousAttribute
= getModifiedAttributes().put(
306 if (previousAttribute
!= null)
307 return previousAttribute
.get();
310 } catch (NamingException e
) {
311 throw new UserDirectoryException(
312 "Cannot get value for attribute " + key
, e
);
317 public Object
remove(Object key
) {
318 userAdmin
.checkEdit();
322 if (includeFilter
&& !attrFilter
.contains(key
))
323 throw new IllegalArgumentException("Key " + key
325 else if (!includeFilter
&& attrFilter
.contains(key
))
326 throw new IllegalArgumentException("Key " + key
+ " excluded");
329 Attribute attr
= getModifiedAttributes().remove(key
.toString());
334 } catch (NamingException e
) {
335 throw new UserDirectoryException("Cannot remove attribute "