]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.security.core/src/org/argeo/osgi/useradmin/LdifUser.java
Fix URI when using default LDIF
[lgpl/argeo-commons.git] / org.argeo.security.core / src / org / argeo / osgi / useradmin / LdifUser.java
1 package org.argeo.osgi.useradmin;
2
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;
13
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;
20
21 import org.apache.commons.codec.binary.Base64;
22 import org.apache.commons.codec.digest.DigestUtils;
23
24 /** Directory user implementation */
25 class LdifUser implements DirectoryUser {
26 private final AbstractUserDirectory userAdmin;
27
28 private final LdapName dn;
29
30 private final boolean frozen;
31 private Attributes publishedAttributes;
32
33 private final AttributeDictionary properties;
34 private final AttributeDictionary credentials;
35
36 LdifUser(AbstractUserDirectory userAdmin, LdapName dn, Attributes attributes) {
37 this(userAdmin, dn, attributes, false);
38 }
39
40 private LdifUser(AbstractUserDirectory userAdmin, LdapName dn,
41 Attributes attributes, boolean frozen) {
42 this.userAdmin = userAdmin;
43 this.dn = dn;
44 this.publishedAttributes = attributes;
45 properties = new AttributeDictionary(false);
46 credentials = new AttributeDictionary(true);
47 this.frozen = frozen;
48 }
49
50 @Override
51 public String getName() {
52 return dn.toString();
53 }
54
55 @Override
56 public int getType() {
57 return USER;
58 }
59
60 @Override
61 public Dictionary<String, Object> getProperties() {
62 return properties;
63 }
64
65 @Override
66 public Dictionary<String, Object> getCredentials() {
67 return credentials;
68 }
69
70 @Override
71 public boolean hasCredential(String key, Object value) {
72 if (key == null) {
73 // TODO check other sources (like PKCS12)
74 char[] password = toChars(value);
75 byte[] hashedPassword = hash(password);
76 return hasCredential(LdifName.userpassword.name(), hashedPassword);
77 }
78
79 Object storedValue = getCredentials().get(key);
80 if (storedValue == null || value == null)
81 return false;
82 if (!(value instanceof String || value instanceof byte[]))
83 return false;
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);
88 return false;
89 }
90
91 /** Hash and clear the password */
92 private byte[] hash(char[] password) {
93 byte[] hashedPassword = ("{SHA}" + Base64
94 .encodeBase64String(DigestUtils.sha1(toBytes(password))))
95 .getBytes();
96 Arrays.fill(password, '\u0000');
97 return hashedPassword;
98 }
99
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
107 return bytes;
108 }
109
110 private char[] toChars(Object obj) {
111 if (obj instanceof char[])
112 return (char[]) obj;
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(),
119 toBuffer.limit());
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
123 return res;
124 }
125
126 @Override
127 public LdapName getDn() {
128 return dn;
129 }
130
131 @Override
132 public synchronized Attributes getAttributes() {
133 return isEditing() ? getModifiedAttributes() : publishedAttributes;
134 }
135
136 /** Should only be called from working copy thread. */
137 private synchronized Attributes getModifiedAttributes() {
138 assert getWc() != null;
139 return getWc().getAttributes(getDn());
140 }
141
142 protected synchronized boolean isEditing() {
143 return getWc() != null && getModifiedAttributes() != null;
144 }
145
146 private synchronized UserDirectoryWorkingCopy getWc() {
147 return userAdmin.getWorkingCopy();
148 }
149
150 protected synchronized void startEditing() {
151 if (frozen)
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();
158 }
159
160 public synchronized void publishAttributes(Attributes modifiedAttributes) {
161 publishedAttributes = modifiedAttributes;
162 }
163
164 // protected synchronized void stopEditing(boolean apply) {
165 // assert getModifiedAttributes() != null;
166 // if (apply)
167 // publishedAttributes = getModifiedAttributes();
168 // // modifiedAttributes = null;
169 // }
170
171 public DirectoryUser getPublished() {
172 return new LdifUser(userAdmin, dn, publishedAttributes, true);
173 }
174
175 @Override
176 public int hashCode() {
177 return dn.hashCode();
178 }
179
180 @Override
181 public boolean equals(Object obj) {
182 if (this == obj)
183 return true;
184 if (obj instanceof LdifUser) {
185 LdifUser that = (LdifUser) obj;
186 return this.dn.equals(that.dn);
187 }
188 return false;
189 }
190
191 @Override
192 public String toString() {
193 return dn.toString();
194 }
195
196 protected AbstractUserDirectory getUserAdmin() {
197 return userAdmin;
198 }
199
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;
204
205 public AttributeDictionary(Boolean includeFilter) {
206 this.attrFilter = userAdmin.getCredentialAttributeIds();
207 this.includeFilter = includeFilter;
208 try {
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);
216 }
217 } catch (NamingException e) {
218 throw new UserDirectoryException(
219 "Cannot initialise attribute dictionary", e);
220 }
221 }
222
223 @Override
224 public int size() {
225 return effectiveKeys.size();
226 }
227
228 @Override
229 public boolean isEmpty() {
230 return effectiveKeys.size() == 0;
231 }
232
233 @Override
234 public Enumeration<String> keys() {
235 return Collections.enumeration(effectiveKeys);
236 }
237
238 @Override
239 public Enumeration<Object> elements() {
240 final Iterator<String> it = effectiveKeys.iterator();
241 return new Enumeration<Object>() {
242
243 @Override
244 public boolean hasMoreElements() {
245 return it.hasNext();
246 }
247
248 @Override
249 public Object nextElement() {
250 String key = it.next();
251 try {
252 return getAttributes().get(key).get();
253 } catch (NamingException e) {
254 throw new UserDirectoryException(
255 "Cannot get value for key " + key, e);
256 }
257 }
258
259 };
260 }
261
262 @Override
263 public Object get(Object key) {
264 try {
265 Attribute attr = getAttributes().get(key.toString());
266 if (attr == null)
267 return null;
268 return attr.get();
269 } catch (NamingException e) {
270 throw new UserDirectoryException(
271 "Cannot get value for attribute " + key, e);
272 }
273 }
274
275 @Override
276 public Object put(String key, Object value) {
277 if (key == null) {
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);
282 }
283
284 userAdmin.checkEdit();
285 if (!isEditing())
286 startEditing();
287
288 if (!(value instanceof String || value instanceof byte[]))
289 throw new IllegalArgumentException(
290 "Value must be String or byte[]");
291
292 if (includeFilter && !attrFilter.contains(key))
293 throw new IllegalArgumentException("Key " + key
294 + " not included");
295 else if (!includeFilter && attrFilter.contains(key))
296 throw new IllegalArgumentException("Key " + key + " excluded");
297
298 try {
299 Attribute attribute = getModifiedAttributes().get(
300 key.toString());
301 attribute = new BasicAttribute(key.toString());
302 attribute.add(value);
303 Attribute previousAttribute = getModifiedAttributes().put(
304 attribute);
305 if (previousAttribute != null)
306 return previousAttribute.get();
307 else
308 return null;
309 } catch (NamingException e) {
310 throw new UserDirectoryException(
311 "Cannot get value for attribute " + key, e);
312 }
313 }
314
315 @Override
316 public Object remove(Object key) {
317 userAdmin.checkEdit();
318 if (!isEditing())
319 startEditing();
320
321 if (includeFilter && !attrFilter.contains(key))
322 throw new IllegalArgumentException("Key " + key
323 + " not included");
324 else if (!includeFilter && attrFilter.contains(key))
325 throw new IllegalArgumentException("Key " + key + " excluded");
326
327 try {
328 Attribute attr = getModifiedAttributes().remove(key.toString());
329 if (attr != null)
330 return attr.get();
331 else
332 return null;
333 } catch (NamingException e) {
334 throw new UserDirectoryException("Cannot remove attribute "
335 + key, e);
336 }
337 }
338 }
339
340 }