View Javadoc
1   package org.argeo.osgi.useradmin;
2   
3   import static org.argeo.naming.LdapAttrs.objectClass;
4   
5   import java.util.ArrayList;
6   import java.util.Dictionary;
7   import java.util.Hashtable;
8   import java.util.List;
9   
10  import javax.naming.Binding;
11  import javax.naming.Context;
12  import javax.naming.InvalidNameException;
13  import javax.naming.NameNotFoundException;
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.DirContext;
19  import javax.naming.directory.SearchControls;
20  import javax.naming.directory.SearchResult;
21  import javax.naming.ldap.InitialLdapContext;
22  import javax.naming.ldap.LdapName;
23  import javax.transaction.TransactionManager;
24  
25  import org.argeo.naming.LdapAttrs;
26  import org.osgi.framework.Filter;
27  import org.osgi.service.useradmin.Role;
28  import org.osgi.service.useradmin.User;
29  
30  /**
31   * A user admin based on a LDAP server. Requires a {@link TransactionManager}
32   * and an open transaction for write access.
33   */
34  public class LdapUserAdmin extends AbstractUserDirectory {
35  	private InitialLdapContext initialLdapContext = null;
36  
37  //	private LdapName adminUserDn = null;
38  //	private LdifUser adminUser = null;
39  
40  	public LdapUserAdmin(Dictionary<String, ?> properties) {
41  		super(null, properties);
42  		try {
43  			Hashtable<String, Object> connEnv = new Hashtable<String, Object>();
44  			connEnv.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
45  			connEnv.put(Context.PROVIDER_URL, getUri().toString());
46  			connEnv.put("java.naming.ldap.attributes.binary", LdapAttrs.userPassword.name());
47  
48  			initialLdapContext = new InitialLdapContext(connEnv, null);
49  			// StartTlsResponse tls = (StartTlsResponse) ctx
50  			// .extendedOperation(new StartTlsRequest());
51  			// tls.negotiate();
52  			Object securityAuthentication = properties.get(Context.SECURITY_AUTHENTICATION);
53  			if (securityAuthentication != null)
54  				initialLdapContext.addToEnvironment(Context.SECURITY_AUTHENTICATION, securityAuthentication);
55  			else
56  				initialLdapContext.addToEnvironment(Context.SECURITY_AUTHENTICATION, "simple");
57  			Object principal = properties.get(Context.SECURITY_PRINCIPAL);
58  			if (principal != null) {
59  				initialLdapContext.addToEnvironment(Context.SECURITY_PRINCIPAL, principal.toString());
60  //				adminUserDn = new LdapName(principal.toString());
61  //				BasicAttributes adminUserAttrs = new BasicAttributes();
62  //				adminUser = new LdifUser(this, adminUserDn, adminUserAttrs);
63  				Object creds = properties.get(Context.SECURITY_CREDENTIALS);
64  				if (creds != null) {
65  					initialLdapContext.addToEnvironment(Context.SECURITY_CREDENTIALS, creds.toString());
66  //					adminUserAttrs.put(LdapAttrs.userPassword.name(), adminUser.hash(creds.toString().toCharArray()));
67  				}
68  //				adminUserAttrs.put(LdapAttrs.memberOf.name(), "cn=admin,ou=roles,ou=node");
69  			}
70  		} catch (Exception e) {
71  			throw new UserDirectoryException("Cannot connect to LDAP", e);
72  		}
73  	}
74  
75  	public void destroy() {
76  		try {
77  			// tls.close();
78  			initialLdapContext.close();
79  		} catch (NamingException e) {
80  			e.printStackTrace();
81  		}
82  	}
83  
84  	@Override
85  	protected AbstractUserDirectory scope(User user) {
86  		Dictionary<String, Object> credentials = user.getCredentials();
87  		String username = (String) credentials.get(SHARED_STATE_USERNAME);
88  		if (username == null)
89  			username = user.getName();
90  		Dictionary<String, Object> properties = cloneProperties();
91  		properties.put(Context.SECURITY_PRINCIPAL, username.toString());
92  		Object pwdCred = credentials.get(SHARED_STATE_PASSWORD);
93  		byte[] pwd = (byte[]) pwdCred;
94  		if (pwd != null) {
95  			char[] password = DigestUtils.bytesToChars(pwd);
96  			properties.put(Context.SECURITY_CREDENTIALS, new String(password));
97  		} else {
98  			properties.put(Context.SECURITY_AUTHENTICATION, "GSSAPI");
99  		}
100 		return new LdapUserAdmin(properties);
101 	}
102 
103 	protected InitialLdapContext getLdapContext() {
104 		return initialLdapContext;
105 	}
106 
107 	@Override
108 	protected Boolean daoHasRole(LdapName dn) {
109 		try {
110 			return daoGetRole(dn) != null;
111 		} catch (NameNotFoundException e) {
112 			return false;
113 		}
114 	}
115 
116 	@Override
117 	protected DirectoryUser daoGetRole(LdapName name) throws NameNotFoundException {
118 		try {
119 			Attributes attrs = getLdapContext().getAttributes(name);
120 			if (attrs.size() == 0)
121 				return null;
122 			int roleType = roleType(name);
123 			LdifUser res;
124 			if (roleType == Role.GROUP)
125 				res = new LdifGroup(this, name, attrs);
126 			else if (roleType == Role.USER)
127 				res = new LdifUser(this, name, attrs);
128 			else
129 				throw new UserDirectoryException("Unsupported LDAP type for " + name);
130 			return res;
131 		} catch (NameNotFoundException e) {
132 //			if (adminUserDn != null && adminUserDn.equals(name)) {
133 //				return adminUser;
134 //			}
135 			throw e;
136 		} catch (NamingException e) {
137 			return null;
138 		}
139 	}
140 
141 	@Override
142 	protected List<DirectoryUser> doGetRoles(Filter f) {
143 		ArrayList<DirectoryUser> res = new ArrayList<DirectoryUser>();
144 		try {
145 			String searchFilter = f != null ? f.toString()
146 					: "(|(" + objectClass + "=" + getUserObjectClass() + ")(" + objectClass + "="
147 							+ getGroupObjectClass() + "))";
148 			SearchControls searchControls = new SearchControls();
149 			searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
150 
151 			LdapName searchBase = getBaseDn();
152 			NamingEnumeration<SearchResult> results = getLdapContext().search(searchBase, searchFilter, searchControls);
153 
154 			results: while (results.hasMoreElements()) {
155 				SearchResult searchResult = results.next();
156 				Attributes attrs = searchResult.getAttributes();
157 				Attribute objectClassAttr = attrs.get(objectClass.name());
158 				LdapName dn = toDn(searchBase, searchResult);
159 				LdifUser role;
160 				if (objectClassAttr.contains(getGroupObjectClass())
161 						|| objectClassAttr.contains(getGroupObjectClass().toLowerCase()))
162 					role = new LdifGroup(this, dn, attrs);
163 				else if (objectClassAttr.contains(getUserObjectClass())
164 						|| objectClassAttr.contains(getUserObjectClass().toLowerCase()))
165 					role = new LdifUser(this, dn, attrs);
166 				else {
167 //					log.warn("Unsupported LDAP type for " + searchResult.getName());
168 					continue results;
169 				}
170 				res.add(role);
171 			}
172 			return res;
173 //		} catch (NameNotFoundException e) {
174 //			return res;
175 		} catch (Exception e) {
176 			throw new UserDirectoryException("Cannot get roles for filter " + f, e);
177 		}
178 	}
179 
180 	private LdapName toDn(LdapName baseDn, Binding binding) throws InvalidNameException {
181 		return new LdapName(binding.isRelative() ? binding.getName() + "," + baseDn : binding.getName());
182 	}
183 
184 	@Override
185 	protected List<LdapName> getDirectGroups(LdapName dn) {
186 		List<LdapName> directGroups = new ArrayList<LdapName>();
187 		try {
188 			String searchFilter = "(&(" + objectClass + "=" + getGroupObjectClass() + ")(" + getMemberAttributeId()
189 					+ "=" + dn + "))";
190 
191 			SearchControls searchControls = new SearchControls();
192 			searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
193 
194 			LdapName searchBase = getBaseDn();
195 			NamingEnumeration<SearchResult> results = getLdapContext().search(searchBase, searchFilter, searchControls);
196 
197 			while (results.hasMoreElements()) {
198 				SearchResult searchResult = (SearchResult) results.nextElement();
199 				directGroups.add(toDn(searchBase, searchResult));
200 			}
201 			return directGroups;
202 		} catch (Exception e) {
203 			throw new UserDirectoryException("Cannot populate direct members of " + dn, e);
204 		}
205 	}
206 
207 	@Override
208 	protected void prepare(UserDirectoryWorkingCopy wc) {
209 		try {
210 			getLdapContext().reconnect(getLdapContext().getConnectControls());
211 			// delete
212 			for (LdapName dn : wc.getDeletedUsers().keySet()) {
213 				if (!entryExists(dn))
214 					throw new UserDirectoryException("User to delete no found " + dn);
215 			}
216 			// add
217 			for (LdapName dn : wc.getNewUsers().keySet()) {
218 				if (entryExists(dn))
219 					throw new UserDirectoryException("User to create found " + dn);
220 			}
221 			// modify
222 			for (LdapName dn : wc.getModifiedUsers().keySet()) {
223 				if (!wc.getNewUsers().containsKey(dn) && !entryExists(dn))
224 					throw new UserDirectoryException("User to modify not found " + dn);
225 			}
226 		} catch (NamingException e) {
227 			throw new UserDirectoryException("Cannot prepare LDAP", e);
228 		}
229 	}
230 
231 	private boolean entryExists(LdapName dn) throws NamingException {
232 		try {
233 			return getLdapContext().getAttributes(dn).size() != 0;
234 		} catch (NameNotFoundException e) {
235 			return false;
236 		}
237 	}
238 
239 	@Override
240 	protected void commit(UserDirectoryWorkingCopy wc) {
241 		try {
242 			// delete
243 			for (LdapName dn : wc.getDeletedUsers().keySet()) {
244 				getLdapContext().destroySubcontext(dn);
245 			}
246 			// add
247 			for (LdapName dn : wc.getNewUsers().keySet()) {
248 				DirectoryUser user = wc.getNewUsers().get(dn);
249 				getLdapContext().createSubcontext(dn, user.getAttributes());
250 			}
251 			// modify
252 			for (LdapName dn : wc.getModifiedUsers().keySet()) {
253 				Attributes modifiedAttrs = wc.getModifiedUsers().get(dn);
254 				getLdapContext().modifyAttributes(dn, DirContext.REPLACE_ATTRIBUTE, modifiedAttrs);
255 			}
256 		} catch (NamingException e) {
257 			throw new UserDirectoryException("Cannot commit LDAP", e);
258 		}
259 	}
260 
261 	@Override
262 	protected void rollback(UserDirectoryWorkingCopy wc) {
263 		// prepare not impacting
264 	}
265 
266 }