1
2
3
4
5
6
7 package gov.bnl.gums.account;
8
9 import gov.bnl.gums.GUMS;
10 import gov.bnl.gums.SiteUser;
11 import gov.bnl.gums.configuration.Configuration;
12
13 import java.util.*;
14
15 import javax.naming.*;
16 import javax.naming.directory.*;
17 import javax.naming.ldap.InitialLdapContext;
18 import javax.persistence.Entity;
19 import javax.persistence.Transient;
20
21 import org.apache.log4j.Logger;
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36 @Entity
37 public class LdapAccountMapper extends AccountMapper {
38 static private Logger log = Logger.getLogger(LdapAccountMapper.class);
39 static private List<DirContext> contexts = Collections.synchronizedList(new LinkedList<DirContext>());
40 static private Logger gumsAdminLog = Logger.getLogger(GUMS.gumsAdminLogName);
41
42 protected boolean skipReleaseContext = false;
43 protected String peopleContext = null;
44 protected String groupContext = null;
45 protected String peopleObject = "ou=People";
46 protected String groupObject = "ou=Group";
47
48
49 protected String jndiLdapUrl;
50 protected String dnField = "description";
51 protected String accountField = "uid";
52 protected String memberUidField = "memberUid";
53 protected String gidNumberField = "gidNumber";
54 protected String groupCnField = "cn";
55 protected String peopleTree;
56 protected String groupTree;
57 protected boolean synchGroups;
58
59 public LdapAccountMapper() {
60 super();
61 }
62
63 public LdapAccountMapper(Configuration configuration, String name) {
64 super(configuration, name);
65 }
66
67
68
69
70
71
72
73 public void addToSecondaryGroup(String account, String groupname) {
74 DirContext context = null;
75 try {
76 context = retrieveGroupContext();
77 SearchControls ctrls = new SearchControls();
78 ctrls.setSearchScope(SearchControls.SUBTREE_SCOPE);
79 NamingEnumeration<SearchResult> result;
80 result = context.search(groupCnField+"="+groupname+","+groupObject, "("+memberUidField+"={0})", new Object[] {account}, ctrls);
81 if (result.hasMore()) return;
82 ModificationItem[] mods = new ModificationItem[1];
83 mods[0] = new ModificationItem(DirContext.ADD_ATTRIBUTE, new BasicAttribute(memberUidField, account));
84 context.modifyAttributes(groupCnField+"="+groupname+","+groupObject, mods);
85 log.trace("Added secondary group to user - user '" + account + "' to group '" + groupname + "'");
86 } catch (Exception e) {
87 log.info("Couldn't add user to secondary group - user '" + account + "' to group '" + groupname + "'", e);
88 throw new RuntimeException("Couldn't add user to secondary group - user '" + account + "' to group '" + groupname + "': " + e.getMessage(), e);
89 } finally {
90 releaseContext(context);
91 }
92 }
93
94
95
96
97
98
99
100 public void changeGroup(String account, String groupname) {
101 try {
102 String gid = findGID(groupname);
103 if (gid == null) {
104 log.error("GID for group '" + groupname + "' wasn't found.");
105 throw new RuntimeException("GID for group '" + groupname + "' wasn't found.");
106 }
107 updateGID(account, gid);
108 }
109 catch(NamingException e) {
110 log.error(e.getMessage());
111 throw new RuntimeException(e.getMessage());
112 }
113 }
114
115 public String getAccountField() {
116 return accountField;
117 }
118
119 public String getDnField() {
120 return dnField;
121 }
122
123 public String getGidNumberField() {
124 return gidNumberField;
125 }
126
127 public String getGroupCnField() {
128 return groupCnField;
129 }
130
131 public String getGroupTree() {
132 return groupTree;
133 }
134
135 public String getJndiLdapUrl() {
136 return jndiLdapUrl;
137 }
138
139 public String getMemberUidField() {
140 return memberUidField;
141 }
142
143 public String getPeopleTree() {
144 return peopleTree;
145 }
146
147 public boolean isSynchGroups() {
148 return synchGroups;
149 }
150
151 public SiteUser mapDn(String dn, boolean createIfDoesNotExist) {
152 Properties jndiProperties = getProperties();
153 String userDNWithSubject = "subject="+dn;
154 int nTries = 5;
155 int i = 0;
156 for (; i < nTries; i++) {
157 log.debug("Attempt " + i + " to retrieve map at '" + jndiLdapUrl + "'");
158 try {
159 DirContext jndiCtx = new InitialDirContext(jndiProperties);
160 NamingEnumeration<SearchResult> nisMap = jndiCtx.search(peopleObject, "("+accountField+"=*)", null);
161 log.debug("Server responded");
162 while (nisMap.hasMore()) {
163 SearchResult res = (SearchResult) nisMap.next();
164 Attributes atts = res.getAttributes();
165 Attribute dnAtt = atts.get(dnField);
166 if (dnAtt != null) {
167 if (dn.equals(dnAtt.get().toString()) || userDNWithSubject.equals(dnAtt.get().toString())) {
168 String account = (String) atts.get(accountField).get();
169 log.debug("Found account '" + account + "' for DN '" + dn + "'");
170 return new SiteUser(account);
171 }
172 }
173 }
174 jndiCtx.close();
175 } catch (javax.naming.NamingException ne) {
176 log.warn("Error searching LDAP at "+jndiLdapUrl, ne);
177 try {
178 Thread.sleep(100);
179 } catch (InterruptedException e) {
180 log.warn("Interrupted", e);
181 }
182 } catch (Exception e) {
183 log.warn("Error searching LDAP at "+jndiLdapUrl, e);
184 try {
185 Thread.sleep(100);
186 } catch (InterruptedException ie) {
187 log.warn("Interrupted", e);
188 }
189 }
190 }
191 return null;
192 }
193
194 public DirContext retrieveGroupContext() {
195 DirContext context;
196 while (contexts.size() != 0) {
197 context = (DirContext) contexts.remove(0);
198 if (isGroupContextValid(context)) {
199 log.trace("Using LDAP connection from pool " + context);
200 return context;
201 }
202 }
203 context = createGroupContext();
204 log.trace("New LDAP connection created " + context);
205 return context;
206 }
207
208 @ConfigFieldAnnotation(label="Account UID Field", example="uid")
209 public void setAccountField(String accountField) {
210 this.accountField = accountField;
211 }
212
213 @ConfigFieldAnnotation (label="Certificate DN Field", example="description")
214 public void setDnField(String dnField) {
215 this.dnField = dnField;
216 }
217
218 @ConfigFieldAnnotation(label="GID Number Field", example="gidNumber", help="group ID number field in 'People' object")
219 public void setGidNumberField(String gidNumberField) {
220 this.gidNumberField = gidNumberField;
221 }
222
223 @ConfigFieldAnnotation(label="Group CN Field", example="cn")
224 public void setGroupCnField(String groupCnField) {
225 this.groupCnField = groupCnField;
226 }
227
228 @ConfigFieldAnnotation(label="Group Tree", example="ou=Group,dc=usatlas,dc=bnl,dc=gov", help="relative to context in LDAP URL - optional")
229 public void setGroupTree(String groupTree) {
230 if (groupTree != null) {
231 this.groupTree = groupTree;
232 if (groupTree.indexOf(',')!=-1) {
233 this.groupObject = groupTree.substring(0, groupTree.indexOf(','));
234 this.groupContext = groupTree.substring(groupTree.indexOf(',')+1);
235 }
236 else
237 this.groupObject = groupTree;
238 }
239 }
240
241 @ConfigFieldAnnotation(label="JNDI LDAP URL", example="ldap://localhost/dc=usatlas,dc=bnl,dc=gov")
242 public void setJndiLdapUrl(String jndiLdapUrl) {
243 this.jndiLdapUrl = jndiLdapUrl;
244 }
245
246 @ConfigFieldAnnotation(label="Member Field", example="memberUid", help="field containing user ID in 'Group' object")
247 public void setMemberUidField(String memberUidField) {
248 this.memberUidField = memberUidField;
249 }
250
251 @ConfigFieldAnnotation(label="People Tree", example="ou=People,dc=usatlas,dc=bnl,dc=gov", help="relative to context in LDAP URL - optional")
252 public void setPeopleTree(String peopleTree) {
253 if (peopleTree != null) {
254 this.peopleTree = peopleTree;
255 if (peopleTree.indexOf(',')!=-1) {
256 this.peopleObject = peopleTree.substring(0, peopleTree.indexOf(','));
257 this.peopleContext = peopleTree.substring(peopleTree.indexOf(',')+1);
258 }
259 else
260 this.peopleObject = peopleTree;
261 }
262 }
263
264 @ConfigFieldAnnotation(label="Update group and email for every access")
265 public void setSynchGroups(boolean synchGroups) {
266 this.synchGroups = synchGroups;
267 }
268
269 protected String findGID(String groupname) throws NamingException {
270 DirContext context = retrieveGroupContext();
271 try {
272 NamingEnumeration<SearchResult> result = context.search(groupObject, "("+groupCnField+"={0})", new Object[] {groupname}, null);
273 String gid = null;
274 if (result.hasMore()) {
275 SearchResult item = result.next();
276 Attributes atts = item.getAttributes();
277 Attribute gidAtt = atts.get(gidNumberField);
278 if (gidAtt != null) {
279 gid = (String) gidAtt.get();
280 }
281 }
282 log.trace("Found gid '" + gid + "' for group '" + groupname + "'");
283 return gid;
284 } catch (Exception e) {
285 log.info("Couldn't retrieve gid for '" + groupname + "'", e);
286 throw new RuntimeException("Couldn't retrieve gid for '" + groupname + "': " + e.getMessage(), e);
287 } finally {
288 releaseContext(context);
289 }
290 }
291
292 protected void updateGID(String account, String gid) throws NamingException {
293 DirContext context = retrievePeopleContext();
294 try {
295 ModificationItem[] mods = new ModificationItem[1];
296 mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute(gidNumberField, gid));
297 context.modifyAttributes(accountField+"="+account+","+peopleObject, mods);
298 log.trace("Changed primary gid for user '" + account + "' to gid '" + gid + "''");
299 } catch (Exception e) {
300 log.warn("Couldn't change gid for user '" + account + "' to gid '" + gid + "''", e);
301 throw new RuntimeException("Couldn't change gid for user '" + account + "' to gid '" + gid + "''", e);
302 } finally {
303 releaseContext(context);
304 }
305 }
306
307
308 protected DirContext createGroupContext() {
309 Properties properties = (Properties)getProperties();
310 try {
311 log.info("Trying to create LDAP connection with properties: " + properties);
312 return (DirContext)new InitialLdapContext(properties, null);
313 } catch (NamingException e) {
314 log.warn("Couldn't create LDAP connection: " + e.getMessage() + " - parameters: " + properties, e);
315 throw new RuntimeException("Couldn't create LDAP connection: " + e.getMessage());
316 }
317 }
318
319 protected DirContext createPeopleContext() {
320 Properties properties = (Properties)getProperties();
321 try {
322 log.info("Trying to create LDAP connection with properties: " + properties);
323 return (DirContext)new InitialLdapContext(properties, null);
324 } catch (NamingException e) {
325 log.warn("Couldn't create LDAP connection: " + e.getMessage() + " - parameters: " + getProperties(), e);
326 throw new RuntimeException("Couldn't create LDAP connection: " + e.getMessage());
327 }
328 }
329
330 @Transient
331 protected Properties getProperties() {
332 Properties properties = new Properties();
333 properties.setProperty("java.naming.provider.url", jndiLdapUrl+(groupContext!=null?"/"+groupContext:""));
334 properties.setProperty("java.naming.factory.initial","com.sun.jndi.ldap.LdapCtxFactory");
335 properties.setProperty(Context.SECURITY_PROTOCOL, "none");
336 System.out.println(properties);
337 return properties;
338 }
339
340 protected boolean isGroupContextValid(DirContext context) {
341 try {
342 context.search(groupObject, "(" + groupCnField + "=*)", null);
343 return true;
344 } catch (Exception e) {
345 log.trace("Removing stale LDAP connection from pool " + context, e);
346 gumsAdminLog.warn("LDAP connection test failed, discarding connection from pool: " + e.getMessage());
347 return false;
348 }
349 }
350
351 protected boolean isPeopleContextValid(DirContext context) {
352 try {
353 context.search(peopleObject, "(" + gidNumberField + "=*)", null);
354 return true;
355 } catch (Exception e) {
356 log.trace("Removing stale LDAP connection from pool " + context, e);
357 gumsAdminLog.warn("LDAP connection test failed, discarding connection from pool: " + e.getMessage());
358 return false;
359 }
360 }
361
362
363
364
365
366 protected void releaseContext(DirContext context) {
367 if (skipReleaseContext) {
368 skipReleaseContext = false;
369 return;
370 }
371 contexts.add(0, context);
372 log.trace("LDAP connection returned to pool " + context);
373 }
374
375 protected DirContext retrievePeopleContext() {
376 DirContext context;
377 while (contexts.size() != 0) {
378 context = (DirContext) contexts.remove(0);
379 if (isPeopleContextValid(context)) {
380 log.trace("Using LDAP connection from pool " + context);
381 return context;
382 }
383 }
384 context = createPeopleContext();
385 log.trace("New LDAP connection created " + context);
386 return context;
387 }
388 }