]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.security.core/src/org/argeo/osgi/useradmin/LdapUserAdmin.java
Standardise user admin unit tests
[lgpl/argeo-commons.git] / org.argeo.security.core / src / org / argeo / osgi / useradmin / LdapUserAdmin.java
1 package org.argeo.osgi.useradmin;
2
3 import static org.argeo.osgi.useradmin.LdifName.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.apache.commons.logging.Log;
26 import org.apache.commons.logging.LogFactory;
27 import org.argeo.ArgeoException;
28 import org.osgi.framework.Filter;
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 final static Log log = LogFactory.getLog(LdapUserAdmin.class);
36
37 private InitialLdapContext initialLdapContext = null;
38
39 public LdapUserAdmin(Dictionary<String, ?> properties) {
40 super(properties);
41 try {
42 Hashtable<String, Object> connEnv = new Hashtable<String, Object>();
43 connEnv.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
44 connEnv.put(Context.PROVIDER_URL, getUri().toString());
45 connEnv.put("java.naming.ldap.attributes.binary", LdifName.userPassword.name());
46
47 initialLdapContext = new InitialLdapContext(connEnv, null);
48 // StartTlsResponse tls = (StartTlsResponse) ctx
49 // .extendedOperation(new StartTlsRequest());
50 // tls.negotiate();
51 initialLdapContext.addToEnvironment(Context.SECURITY_AUTHENTICATION, "simple");
52 Object principal = properties.get(Context.SECURITY_PRINCIPAL);
53 if (principal != null) {
54 initialLdapContext.addToEnvironment(Context.SECURITY_PRINCIPAL, principal.toString());
55 Object creds = properties.get(Context.SECURITY_CREDENTIALS);
56 if (creds != null) {
57 initialLdapContext.addToEnvironment(Context.SECURITY_CREDENTIALS, creds.toString());
58
59 }
60 }
61 // initialLdapContext.addToEnvironment(Context.SECURITY_PRINCIPAL,
62 // "uid=admin,ou=system");
63 // initialLdapContext.addToEnvironment(Context.SECURITY_CREDENTIALS,
64 // "secret");
65 } catch (Exception e) {
66 throw new UserDirectoryException("Cannot connect to LDAP", e);
67 }
68 }
69
70 public void destroy() {
71 try {
72 // tls.close();
73 initialLdapContext.close();
74 } catch (NamingException e) {
75 log.error("Cannot destroy LDAP user admin", e);
76 }
77 }
78
79 protected InitialLdapContext getLdapContext() {
80 return initialLdapContext;
81 }
82
83 @Override
84 protected Boolean daoHasRole(LdapName dn) {
85 return daoGetRole(dn) != null;
86 }
87
88 @Override
89 protected DirectoryUser daoGetRole(LdapName name) {
90 try {
91 Attributes attrs = getLdapContext().getAttributes(name);
92 if (attrs.size() == 0)
93 return null;
94 LdifUser res;
95 if (attrs.get(objectClass.name()).contains(getGroupObjectClass()))
96 res = new LdifGroup(this, name, attrs);
97 else if (attrs.get(objectClass.name()).contains(getUserObjectClass()))
98 res = new LdifUser(this, name, attrs);
99 else
100 throw new UserDirectoryException("Unsupported LDAP type for " + name);
101 return res;
102 } catch (NamingException e) {
103 return null;
104 }
105 }
106
107 @Override
108 protected List<DirectoryUser> doGetRoles(Filter f) {
109 try {
110 String searchFilter = f != null ? f.toString()
111 : "(|(" + objectClass + "=" + getUserObjectClass() + ")(" + objectClass + "="
112 + getGroupObjectClass() + "))";
113 SearchControls searchControls = new SearchControls();
114 searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
115
116 String searchBase = getBaseDn();
117 NamingEnumeration<SearchResult> results = getLdapContext().search(searchBase, searchFilter, searchControls);
118
119 ArrayList<DirectoryUser> res = new ArrayList<DirectoryUser>();
120 results: while (results.hasMoreElements()) {
121 SearchResult searchResult = results.next();
122 Attributes attrs = searchResult.getAttributes();
123 Attribute objectClassAttr = attrs.get(objectClass.name());
124 LdapName dn = toDn(searchBase, searchResult);
125 LdifUser role;
126 if (objectClassAttr.contains(getGroupObjectClass()))
127 role = new LdifGroup(this, dn, attrs);
128 else if (objectClassAttr.contains(getUserObjectClass()))
129 role = new LdifUser(this, dn, attrs);
130 else {
131 log.warn("Unsupported LDAP type for " + searchResult.getName());
132 continue results;
133 }
134 res.add(role);
135 }
136 return res;
137 } catch (Exception e) {
138 throw new UserDirectoryException("Cannot get roles for filter " + f, e);
139 }
140 }
141
142 private LdapName toDn(String baseDn, Binding binding) throws InvalidNameException {
143 return new LdapName(binding.isRelative() ? binding.getName() + "," + baseDn : binding.getName());
144 }
145
146 @Override
147 protected List<LdapName> getDirectGroups(LdapName dn) {
148 List<LdapName> directGroups = new ArrayList<LdapName>();
149 try {
150 String searchFilter = "(&(" + objectClass + "=" + getGroupObjectClass() + ")(" + getMemberAttributeId()
151 + "=" + dn + "))";
152
153 SearchControls searchControls = new SearchControls();
154 searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
155
156 String searchBase = getBaseDn();
157 NamingEnumeration<SearchResult> results = getLdapContext().search(searchBase, searchFilter, searchControls);
158
159 while (results.hasMoreElements()) {
160 SearchResult searchResult = (SearchResult) results.nextElement();
161 directGroups.add(toDn(searchBase, searchResult));
162 }
163 return directGroups;
164 } catch (Exception e) {
165 throw new ArgeoException("Cannot populate direct members of " + dn, e);
166 }
167 }
168
169 @Override
170 protected void prepare(UserDirectoryWorkingCopy wc) {
171 try {
172 getLdapContext().reconnect(getLdapContext().getConnectControls());
173 // delete
174 for (LdapName dn : wc.getDeletedUsers().keySet()) {
175 if (!entryExists(dn))
176 throw new UserDirectoryException("User to delete no found " + dn);
177 }
178 // add
179 for (LdapName dn : wc.getNewUsers().keySet()) {
180 if (entryExists(dn))
181 throw new UserDirectoryException("User to create found " + dn);
182 }
183 // modify
184 for (LdapName dn : wc.getModifiedUsers().keySet()) {
185 if (!wc.getNewUsers().containsKey(dn) && !entryExists(dn))
186 throw new UserDirectoryException("User to modify not found " + dn);
187 }
188 } catch (NamingException e) {
189 throw new UserDirectoryException("Cannot prepare LDAP", e);
190 }
191 }
192
193 private boolean entryExists(LdapName dn) throws NamingException {
194 try {
195 return getLdapContext().getAttributes(dn).size() != 0;
196 } catch (NameNotFoundException e) {
197 return false;
198 }
199 }
200
201 @Override
202 protected void commit(UserDirectoryWorkingCopy wc) {
203 try {
204 // delete
205 for (LdapName dn : wc.getDeletedUsers().keySet()) {
206 getLdapContext().destroySubcontext(dn);
207 }
208 // add
209 for (LdapName dn : wc.getNewUsers().keySet()) {
210 DirectoryUser user = wc.getNewUsers().get(dn);
211 getLdapContext().createSubcontext(dn, user.getAttributes());
212 }
213 // modify
214 for (LdapName dn : wc.getModifiedUsers().keySet()) {
215 Attributes modifiedAttrs = wc.getModifiedUsers().get(dn);
216 getLdapContext().modifyAttributes(dn, DirContext.REPLACE_ATTRIBUTE, modifiedAttrs);
217 }
218 } catch (NamingException e) {
219 throw new UserDirectoryException("Cannot commit LDAP", e);
220 }
221 }
222
223 @Override
224 protected void rollback(UserDirectoryWorkingCopy wc) {
225 // prepare not impacting
226 }
227
228 }