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 class LdifUser
implements DirectoryUser
{
26 private final AbstractUserDirectory userAdmin
;
28 private final LdapName dn
;
30 private final boolean frozen
;
31 private Attributes publishedAttributes
;
33 private final AttributeDictionary properties
;
34 private final AttributeDictionary credentials
;
36 LdifUser(AbstractUserDirectory userAdmin
, LdapName dn
, Attributes attributes
) {
37 this(userAdmin
, dn
, attributes
, false);
40 private LdifUser(AbstractUserDirectory userAdmin
, LdapName dn
,
41 Attributes attributes
, boolean frozen
) {
42 this.userAdmin
= userAdmin
;
44 this.publishedAttributes
= attributes
;
45 properties
= new AttributeDictionary(false);
46 credentials
= new AttributeDictionary(true);
51 public String
getName() {
56 public int getType() {
61 public Dictionary
<String
, Object
> getProperties() {
66 public Dictionary
<String
, Object
> getCredentials() {
71 public boolean hasCredential(String key
, Object value
) {
73 // TODO check other sources (like PKCS12)
74 char[] password
= toChars(value
);
75 byte[] hashedPassword
= hash(password
);
76 return hasCredential(LdifName
.userpassword
.name(), hashedPassword
);
79 Object storedValue
= getCredentials().get(key
);
80 if (storedValue
== null || value
== null)
82 if (!(value
instanceof String
|| value
instanceof byte[]))
84 if (storedValue
instanceof String
&& value
instanceof String
)
85 return storedValue
.equals(value
);
86 if (storedValue
instanceof byte[] && value
instanceof byte[])
87 return Arrays
.equals((byte[]) storedValue
, (byte[]) value
);
91 /** Hash and clear the password */
92 private byte[] hash(char[] password
) {
93 byte[] hashedPassword
= ("{SHA}" + Base64
94 .encodeBase64String(DigestUtils
.sha1(toBytes(password
))))
96 Arrays
.fill(password
, '\u0000');
97 return hashedPassword
;
100 private byte[] toBytes(char[] chars
) {
101 CharBuffer charBuffer
= CharBuffer
.wrap(chars
);
102 ByteBuffer byteBuffer
= Charset
.forName("UTF-8").encode(charBuffer
);
103 byte[] bytes
= Arrays
.copyOfRange(byteBuffer
.array(),
104 byteBuffer
.position(), byteBuffer
.limit());
105 Arrays
.fill(charBuffer
.array(), '\u0000'); // clear sensitive data
106 Arrays
.fill(byteBuffer
.array(), (byte) 0); // clear sensitive data
110 private char[] toChars(Object obj
) {
111 if (obj
instanceof char[])
113 if (!(obj
instanceof byte[]))
114 throw new IllegalArgumentException(obj
.getClass()
115 + " is not a byte array");
116 ByteBuffer fromBuffer
= ByteBuffer
.wrap((byte[]) obj
);
117 CharBuffer toBuffer
= Charset
.forName("UTF-8").decode(fromBuffer
);
118 char[] res
= Arrays
.copyOfRange(toBuffer
.array(), toBuffer
.position(),
120 Arrays
.fill(fromBuffer
.array(), (byte) 0); // clear sensitive data
121 Arrays
.fill((byte[]) obj
, (byte) 0); // clear sensitive data
122 Arrays
.fill(toBuffer
.array(), '\u0000'); // clear sensitive data
127 public LdapName
getDn() {
132 public synchronized Attributes
getAttributes() {
133 return isEditing() ?
getModifiedAttributes() : publishedAttributes
;
136 /** Should only be called from working copy thread. */
137 private synchronized Attributes
getModifiedAttributes() {
138 assert getWc() != null;
139 return getWc().getAttributes(getDn());
142 protected synchronized boolean isEditing() {
143 return getWc() != null && getModifiedAttributes() != null;
146 private synchronized WorkingCopy
getWc() {
147 return userAdmin
.getWorkingCopy();
150 protected synchronized void startEditing() {
152 throw new UserDirectoryException("Cannot edit frozen view");
153 if (getUserAdmin().isReadOnly())
154 throw new UserDirectoryException("User directory is read-only");
155 assert getModifiedAttributes() == null;
156 getWc().startEditing(this);
157 // modifiedAttributes = (Attributes) publishedAttributes.clone();
160 public synchronized void publishAttributes(Attributes modifiedAttributes
) {
161 publishedAttributes
= modifiedAttributes
;
164 // protected synchronized void stopEditing(boolean apply) {
165 // assert getModifiedAttributes() != null;
167 // publishedAttributes = getModifiedAttributes();
168 // // modifiedAttributes = null;
171 public DirectoryUser
getPublished() {
172 return new LdifUser(userAdmin
, dn
, publishedAttributes
, true);
176 public int hashCode() {
177 return dn
.hashCode();
181 public boolean equals(Object obj
) {
184 if (obj
instanceof LdifUser
) {
185 LdifUser that
= (LdifUser
) obj
;
186 return this.dn
.equals(that
.dn
);
192 public String
toString() {
193 return dn
.toString();
196 protected AbstractUserDirectory
getUserAdmin() {
200 private class AttributeDictionary
extends Dictionary
<String
, Object
> {
201 private final List
<String
> effectiveKeys
= new ArrayList
<String
>();
202 private final List
<String
> attrFilter
;
203 private final Boolean includeFilter
;
205 public AttributeDictionary(Boolean includeFilter
) {
206 this.attrFilter
= userAdmin
.getCredentialAttributeIds();
207 this.includeFilter
= includeFilter
;
209 NamingEnumeration
<String
> ids
= getAttributes().getIDs();
210 while (ids
.hasMore()) {
211 String id
= ids
.next();
212 if (includeFilter
&& attrFilter
.contains(id
))
213 effectiveKeys
.add(id
);
214 else if (!includeFilter
&& !attrFilter
.contains(id
))
215 effectiveKeys
.add(id
);
217 } catch (NamingException e
) {
218 throw new UserDirectoryException(
219 "Cannot initialise attribute dictionary", e
);
225 return effectiveKeys
.size();
229 public boolean isEmpty() {
230 return effectiveKeys
.size() == 0;
234 public Enumeration
<String
> keys() {
235 return Collections
.enumeration(effectiveKeys
);
239 public Enumeration
<Object
> elements() {
240 final Iterator
<String
> it
= effectiveKeys
.iterator();
241 return new Enumeration
<Object
>() {
244 public boolean hasMoreElements() {
249 public Object
nextElement() {
250 String key
= it
.next();
252 return getAttributes().get(key
).get();
253 } catch (NamingException e
) {
254 throw new UserDirectoryException(
255 "Cannot get value for key " + key
, e
);
263 public Object
get(Object key
) {
265 Attribute attr
= getAttributes().get(key
.toString());
269 } catch (NamingException e
) {
270 throw new UserDirectoryException(
271 "Cannot get value for attribute " + key
, e
);
276 public Object
put(String key
, Object value
) {
278 // TODO persist to other sources (like PKCS12)
279 char[] password
= toChars(value
);
280 byte[] hashedPassword
= hash(password
);
281 return put(LdifName
.userpassword
.name(), hashedPassword
);
284 userAdmin
.checkEdit();
288 if (!(value
instanceof String
|| value
instanceof byte[]))
289 throw new IllegalArgumentException(
290 "Value must be String or byte[]");
292 if (includeFilter
&& !attrFilter
.contains(key
))
293 throw new IllegalArgumentException("Key " + key
295 else if (!includeFilter
&& attrFilter
.contains(key
))
296 throw new IllegalArgumentException("Key " + key
+ " excluded");
299 Attribute attribute
= getModifiedAttributes().get(
301 attribute
= new BasicAttribute(key
.toString());
302 attribute
.add(value
);
303 Attribute previousAttribute
= getModifiedAttributes().put(
305 if (previousAttribute
!= null)
306 return previousAttribute
.get();
309 } catch (NamingException e
) {
310 throw new UserDirectoryException(
311 "Cannot get value for attribute " + key
, e
);
316 public Object
remove(Object key
) {
317 userAdmin
.checkEdit();
321 if (includeFilter
&& !attrFilter
.contains(key
))
322 throw new IllegalArgumentException("Key " + key
324 else if (!includeFilter
&& attrFilter
.contains(key
))
325 throw new IllegalArgumentException("Key " + key
+ " excluded");
328 Attribute attr
= getModifiedAttributes().remove(key
.toString());
333 } catch (NamingException e
) {
334 throw new UserDirectoryException("Cannot remove attribute "