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