001: /*
002: * $Id: LDAPRealm.java,v 1.12 2002/09/16 08:05:06 jkl Exp $
003: *
004: * Copyright (c) 2002 Njet Communications Ltd. All Rights Reserved.
005: *
006: * Use is subject to license terms, as defined in
007: * Anvil Sofware License, Version 1.1. See LICENSE
008: * file, or http://njet.org/license-1.1.txt
009: */
010: package anvil.server.ldap;
011:
012: import anvil.Log;
013: import anvil.java.util.BindingEnumeration;
014: import anvil.java.security.PermissionCollectionCombiner;
015: import anvil.core.Any;
016: import anvil.core.Serialization;
017: import anvil.java.util.Hashlist;
018: import anvil.core.Array;
019: import anvil.database.ConnectionManager;
020: import anvil.database.PooledConnection;
021:
022: import anvil.server.PolicyPreferences;
023: import anvil.server.RealmPreferences;
024: import anvil.server.Citizen;
025: import anvil.server.Realm;
026: import anvil.server.Tribe;
027: import anvil.server.Zone;
028: import anvil.server.Realm;
029: import anvil.server.Tribe;
030: import anvil.server.Citizen;
031: import anvil.server.OperationFailedException;
032: import anvil.server.CitizenNotFoundException;
033:
034: import java.io.IOException;
035: import java.util.StringTokenizer;
036: import java.util.Set;
037: import java.util.Map;
038: import java.util.HashSet;
039: import java.util.HashMap;
040: import java.util.List;
041: import java.util.Enumeration;
042: import java.util.ArrayList;
043: import java.security.Permission;
044: import java.security.Permissions;
045: import java.security.PermissionCollection;
046:
047: import org.apache.oro.text.regex.Pattern;
048: import org.apache.oro.text.regex.Perl5Compiler;
049: import org.apache.oro.text.regex.Perl5Matcher;
050: import org.apache.oro.text.regex.MalformedPatternException;
051:
052: import javax.naming.*;
053: import javax.naming.directory.*;
054:
055: /**
056: * class LDAPRealm
057: * Implements Anvil Realm architecture over LDAP.
058: *
059: * TODO:
060: * - tribe.attach(tribe) loop check
061: * - construct ctz.displayName from first+surname/username
062: *
063: * @author: Simo Tuokko
064: */
065:
066: public class LDAPRealm implements Realm {
067:
068: private Map _users = new HashMap();
069: private Map _groups = new HashMap();
070: private Zone _zone;
071: private String _name;
072: private String _prefix = null;
073: private String _contextPool = "authen";
074: private LDAPTribe _rootTribe = null;
075:
076: private ConnectionManager _manager = null;
077:
078: private static String ROOT_TRIBE = "Root Tribe";
079: private static Pattern EMAIL_PATTERN;
080: private static Attribute objectClassAttr;
081: private static Attribute groupObjectClass;
082: static {
083: try {
084: EMAIL_PATTERN = new Perl5Compiler()
085: .compile("^([a-z0-9_\\.\\-])+\\@(([a-z0-9\\-])+\\.)+([a-z0-9]){2,4}$");
086: } catch (MalformedPatternException e) {
087: }
088:
089: objectClassAttr = new BasicAttribute("objectclass");
090: objectClassAttr.add("top");
091: objectClassAttr.add("person");
092: objectClassAttr.add("organizationalPerson");
093: objectClassAttr.add("inetorgperson");
094:
095: groupObjectClass = new BasicAttribute("objectclass");
096: groupObjectClass.add("top");
097: groupObjectClass.add("groupofuniquenames");
098: }
099:
100: public LDAPRealm() {
101: }
102:
103: public String toString() {
104: return "LDAPRealm(" + _name + "@" + _contextPool + "@"
105: + _prefix + ")";
106: }
107:
108: public void initialize(RealmPreferences prefs) {
109: _name = prefs.getName();
110: _zone = prefs.getParent();
111:
112: //get connection manager for contextPool
113: _contextPool = (String) prefs.getPreference("contextPool");
114: _prefix = (String) prefs.getPreference("prefix");
115:
116: if (_prefix == null) {
117: _zone
118: .log()
119: .error(
120: "LDAPRealm: 'prefix' not found from configuration!");
121: }
122: if (_contextPool == null) {
123: _zone
124: .log()
125: .error(
126: "LDAPRealm: contextPool name not found from configuration, using default: authen");
127: }
128: try {
129: _manager = _zone.getManagerFor(_contextPool);
130: } catch (Exception e) {
131: _zone.log().error(
132: "LDAPRealm: error while getting ConnectionManager: "
133: + e);
134: }
135:
136: _zone.log().info("Realm " + this + " initialized");
137: }
138:
139: public void stop() {
140: _zone.log().info("Realm " + this + " stopped");
141: }
142:
143: public Citizen getCitizen(String username) {
144: Citizen c = null;
145:
146: if (!_users.containsKey(username)) {
147: try {
148: c = new LDAPCitizen(this , username);
149: _users.put(username, c);
150: } catch (CitizenNotFoundException cnfe) {
151: //return null
152:
153: } catch (OperationFailedException e) {
154: _zone.log()
155: .error("LDAPRealm.getCitizen() failed: " + e);
156: }
157: } else {
158: c = (Citizen) _users.get(username);
159: }
160:
161: return c;
162: }
163:
164: public Tribe getTribe(String name) {
165: Tribe t = null;
166:
167: if (!_groups.containsKey(name)) {
168: try {
169: t = new LDAPTribe(this , name);
170: _groups.put(name, t);
171: } catch (CitizenNotFoundException cnfe) {
172: _zone.log().error(
173: "LDAPRealm.getTribe() Tribe '" + name
174: + "' not found!");
175:
176: } catch (OperationFailedException e) {
177: _zone.log().error("LDAPRealm.getTribe() failed: " + e);
178: }
179: } else {
180: t = (Tribe) _groups.get(name);
181: }
182:
183: return t;
184: }
185:
186: public Citizen[] searchCitizenByVariable(String variable,
187: String value) {
188: if (variable == null
189: || !LDAPCitizen.ATTRMAP_C.containsKey(variable)) {
190: _zone.log()
191: .error(
192: "LDAPRealm doesn't support search for: "
193: + variable);
194: return null;
195: }
196:
197: PooledConnection connImpl = null;
198: DirContext ctx = null;
199: ArrayList result = new ArrayList();
200:
201: try {
202: connImpl = getConnection();
203: ctx = (DirContext) connImpl.getConnection();
204:
205: String search = "(" + LDAPCitizen.ATTRMAP_C.get(variable)
206: + "=" + value + ")";
207: SearchControls sc = new SearchControls();
208: sc.setReturningAttributes(new String[] { "uid" });
209: NamingEnumeration enu = ctx.search("ou=users", search, sc);
210:
211: while (enu.hasMore()) {
212: SearchResult s = (SearchResult) enu.next();
213: Attributes attrs = s.getAttributes();
214: Attribute uid = attrs.get("uid");
215: if (uid != null) {
216: result.add(getCitizen((String) uid.get()));
217: }
218: }
219:
220: return (Citizen[]) result
221: .toArray(new Citizen[result.size()]);
222:
223: } catch (Exception e) {
224: _zone.log().error(
225: "LDAPRealm.searchCitizenByVariable() failed: " + e);
226:
227: } finally {
228: cleanupContext(connImpl);
229: }
230: return null;
231: }
232:
233: public Citizen getAnonymousCitizen() {
234: return null;
235: }
236:
237: public Tribe getRoot() {
238: if (_rootTribe == null) {
239: synchronized (this ) {
240: if (_rootTribe == null) {
241: try {
242: _rootTribe = new LDAPTribe(this , ROOT_TRIBE,
243: true);
244: } catch (OperationFailedException e) {
245: _zone.log().error("LDAPRealm.getRoot(): " + e);
246: }
247: }
248: }
249: }
250:
251: return _rootTribe;
252: }
253:
254: Tribe[] getMemberGroups(String dn) {
255: PooledConnection connImpl = null;
256: DirContext ctx = null;
257: List groups = new ArrayList();
258:
259: try {
260: connImpl = _manager.acquire(_contextPool);
261: ctx = (DirContext) connImpl.getConnection();
262:
263: NamingEnumeration enu = null;
264: SearchControls sc = new SearchControls();
265: sc.setReturningAttributes(new String[] { "cn" });
266:
267: enu = ctx.search("ou=groups", "(uniqueMember=" + dn + ")",
268: sc);
269:
270: while (enu.hasMore()) {
271: SearchResult s = (SearchResult) enu.next();
272: Attributes a = s.getAttributes();
273: Attribute cn = a.get("cn");
274: if (cn != null) {
275: groups.add(getTribe((String) cn.get()));
276: }
277: }
278: return (Tribe[]) groups.toArray(new Tribe[groups.size()]);
279:
280: } catch (Exception e) {
281: _zone.log()
282: .error("LDAPRealm.getMemberGroups() error: " + e);
283:
284: } finally {
285: cleanupContext(connImpl);
286: }
287:
288: return null;
289: }
290:
291: public Tribe createTribe(String name)
292: throws OperationFailedException {
293: if (name == null)
294: throw new OperationFailedException("Null tribe name!");
295:
296: PooledConnection connImpl = null;
297: DirContext ctx = null;
298:
299: try {
300: connImpl = _manager.acquire(_contextPool);
301: ctx = (DirContext) connImpl.getConnection();
302:
303: Attributes at = new BasicAttributes();
304: at.put(groupObjectClass);
305:
306: at.put("cn", name);
307:
308: ctx.bind("cn=" + name + ",ou=groups", null, at);
309:
310: //"refresh" root tribe
311: if (_rootTribe != null) {
312: _rootTribe.refreshCitizens();
313: }
314:
315: return (Tribe) getTribe(name);
316:
317: } catch (NameAlreadyBoundException e) {
318: throw new OperationFailedException("Group '" + name
319: + "' already exists!");
320:
321: } catch (Exception e) {
322: _zone.log().error("LDAPRealm.createTribe(): " + e);
323:
324: } finally {
325: cleanupContext(connImpl);
326: }
327:
328: return null;
329: }
330:
331: public Citizen createCitizen(String username, String password)
332: throws OperationFailedException {
333: return createCitizen(username, password, null);
334: }
335:
336: public Citizen createCitizen(String username, String password,
337: String[][] params) throws OperationFailedException {
338: if (username == null)
339: throw new OperationFailedException("Null username!");
340: username = username.trim().toLowerCase();
341: if (!checkEmail(username))
342: throw new OperationFailedException(
343: "Username isn't a valid email address!");
344:
345: PooledConnection connImpl = null;
346: DirContext ctx = null;
347:
348: try {
349: connImpl = _manager.acquire(_contextPool);
350: ctx = (DirContext) connImpl.getConnection();
351:
352: Attributes at = new BasicAttributes();
353: at.put("facsimileTelephoneNumber", password);
354: at.put(objectClassAttr);
355:
356: Array others = new Array();
357: if (params != null) {
358: for (int i = 0, l = params.length; i < l; i++) {
359: String key = params[i][0];
360: String val = params[i][1];
361:
362: if (LDAPCitizen.ATTRMAP_C.containsKey(key)) {
363: at.put((String) LDAPCitizen.ATTRMAP_C.get(key),
364: val);
365: //System.err.println ("Save: OK attr: "+LDAPCitizen.ATTRMAP_C.get(key)+" = "+val);
366: } else {
367: others.put(Any.create(key), Any.create(val));
368: //System.err.println ("Save: Unknown attr: "+LDAPCitizen.ATTRMAP_C.get(key)+" = "+val);
369: }
370: }
371: }
372:
373: at.put("cn", username);
374: if (at.get("sn") == null) {
375: at.put("sn", username);
376: }
377: if (others.size() > 0) {
378: try {
379: String sothers = Serialization.serialize(null,
380: others);
381: at.put(LDAPCitizen.OTHERS_ATTR, sothers);
382: } catch (IOException ioe) {
383: throw new OperationFailedException(
384: "Data serialization failed: " + ioe);
385: }
386: }
387:
388: ctx.bind("uid=" + username + ",ou=users", null, at);
389:
390: //"refresh" root tribe
391: if (_rootTribe != null) {
392: _rootTribe.refreshCitizens();
393: }
394:
395: return (Citizen) getCitizen(username);
396:
397: } catch (NameAlreadyBoundException e) {
398: throw new OperationFailedException("User '" + username
399: + "' already exists!");
400:
401: } catch (Exception e) {
402: _zone.log().error("LDAPRealm.createCitizen(): " + e);
403:
404: } finally {
405: cleanupContext(connImpl);
406: }
407:
408: return null;
409: }
410:
411: public void setRoot(Tribe tribe) throws OperationFailedException {
412: throw new OperationFailedException(
413: "This implementation doesn't support setting of root tribe!");
414: }
415:
416: void removeCitizen(String username) {
417: if (_users.containsKey(username)) {
418: _users.remove(username);
419:
420: if (_rootTribe != null) {
421: _rootTribe.refreshCitizens();
422: }
423: }
424: }
425:
426: PooledConnection getConnection() throws Exception {
427: return _manager.acquire(_contextPool);
428: }
429:
430: Log getLog() {
431: return _zone.log();
432: }
433:
434: static void cleanupContext(PooledConnection connImpl) {
435: if (connImpl != null) {
436: connImpl.release();
437: }
438: }
439:
440: public String getPrefix() {
441: return _prefix;
442: }
443:
444: public String createUserDN(String name) {
445: StringBuffer buf = new StringBuffer();
446: buf.append("uid=");
447: buf.append(name);
448: buf.append(",ou=users");
449: if (_prefix != null && _prefix.length() > 0) {
450: buf.append(",");
451: buf.append(_prefix);
452: }
453: return buf.toString();
454: }
455:
456: public String createGroupDN(String name) {
457: StringBuffer buf = new StringBuffer();
458: buf.append("cn=");
459: buf.append(name);
460: buf.append(",ou=groups");
461: if (_prefix != null && _prefix.length() > 0) {
462: buf.append(",");
463: buf.append(_prefix);
464: }
465: return buf.toString();
466: }
467:
468: private boolean checkEmail(String email) {
469: return new Perl5Matcher().matches(email, EMAIL_PATTERN);
470: }
471:
472: PermissionCollection loadPermissions(String dn) {
473: PooledConnection connImpl = null;
474: DirContext ctx = null;
475:
476: try {
477: connImpl = getConnection();
478: ctx = (DirContext) connImpl.getConnection();
479: return loadPermissions(ctx, dn);
480:
481: } catch (Exception e) {
482: _zone.log().error("LDAPRealm.loadPermissions(): " + e);
483:
484: } finally {
485: cleanupContext(connImpl);
486: }
487: return null;
488: }
489:
490: static PermissionCollection loadPermissions(DirContext ctx,
491: String dn) throws Exception {
492: Permissions result = new Permissions();
493: Attributes attrs = ctx.getAttributes(dn,
494: new String[] { "description" });
495: Attribute desc = attrs.get("description");
496: if (desc != null) {
497: for (int i = 0, l = desc.size(); i < l; i++) {
498: try {
499: String[] permSt = parsePermission((String) desc
500: .get(i));
501: result.add(PolicyPreferences
502: .createPermission(permSt));
503: } catch (Throwable t) {
504: throw new OperationFailedException(
505: "LDAPRealm.loadPermissions(): "
506: + t.getMessage());
507: }
508: }
509: }
510: return result;
511: }
512:
513: PermissionCollection addPermission(Permission perm, String dn)
514: throws OperationFailedException {
515: PooledConnection connImpl = null;
516: DirContext ctx = null;
517:
518: try {
519: connImpl = getConnection();
520: ctx = (DirContext) connImpl.getConnection();
521:
522: PermissionCollection storage = LDAPRealm.loadPermissions(
523: ctx, dn);
524: boolean found = false;
525: for (Enumeration enu = storage.elements(); enu
526: .hasMoreElements();) {
527: if (perm.equals(enu.nextElement())) {
528: _zone.log().error(
529: "--addPermission() we already have: "
530: + perm);
531: found = true;
532: }
533: }
534: storage.add(perm);
535: if (!found) {
536: LDAPRealm.savePermissions(ctx, dn, storage);
537: _zone.log().error(
538: "--addPermissions() perm added: " + perm);
539: }
540: return storage;
541:
542: } catch (Exception e) {
543: throw new OperationFailedException(e.getMessage());
544:
545: } finally {
546: cleanupContext(connImpl);
547: }
548: }
549:
550: PermissionCollection removePermission(Permission perm, String dn)
551: throws OperationFailedException {
552: PooledConnection connImpl = null;
553: DirContext ctx = null;
554:
555: try {
556: connImpl = getConnection();
557: ctx = (DirContext) connImpl.getConnection();
558:
559: PermissionCollection storage = LDAPRealm.loadPermissions(
560: ctx, dn);
561: PermissionCollection altered = new Permissions();
562:
563: boolean found = false;
564: for (Enumeration enu = storage.elements(); enu
565: .hasMoreElements();) {
566: Permission p = (Permission) enu.nextElement();
567: if (perm.equals(p)) {
568: found = true;
569: } else {
570: altered.add(p);
571: }
572: }
573: if (found) {
574: LDAPRealm.savePermissions(ctx, dn, altered);
575: _zone.log().error(
576: "--removePermissions() perm removed: "
577: + altered);
578: } else {
579: _zone.log().error(
580: "--removePermissions() kusee.." + altered);
581: }
582: return altered;
583:
584: } catch (Exception e) {
585: throw new OperationFailedException(e.getMessage());
586:
587: } finally {
588: cleanupContext(connImpl);
589: }
590: }
591:
592: static void savePermissions(DirContext ctx, String dn,
593: PermissionCollection perms) throws Exception {
594: Attribute attr = new BasicAttribute("description");
595: Attributes attrs = new BasicAttributes();
596: for (Enumeration enu = perms.elements(); enu.hasMoreElements();) {
597: attr.add(createPermissionString((Permission) enu
598: .nextElement()));
599: }
600: attrs.put(attr);
601: ctx.modifyAttributes(dn, DirContext.REPLACE_ATTRIBUTE, attrs);
602: }
603:
604: static String createPermissionString(Permission p) {
605: StringBuffer b = new StringBuffer();
606: String type = p.getClass().getName();
607: String tmp = null;
608:
609: if (type.equals("anvil.core.ToolPermission")) {
610: b.append("tool");
611:
612: } else if (type.equals("anvil.core.RuntimePermission")) {
613: b.append("runtime");
614:
615: } else if (type.equals("java.io.FilePermission")) {
616: b.append("file");
617:
618: } else if (type.equals("java.net.SocketPermission")) {
619: b.append("socket");
620:
621: } else if (type.equals("anvil.java.security.JavaPermission")) {
622: b.append("java");
623:
624: } else if (type
625: .equals("anvil.database.ConnectionPoolPermission")) {
626: b.append("pool");
627:
628: } else if (type.equals("anvil.script.ImportPermission")) {
629: b.append("import");
630:
631: } else if (type.equals("anvil.server.RealmPermission")) {
632: b.append("realm");
633:
634: } else if (type.equals("anvil.server.NamespacePermission")) {
635: b.append("namespace");
636:
637: } else if (type.equals("java.security.AllPermission")) {
638: b.append("all");
639:
640: } else {
641: b.append(type);
642: }
643: b.append("|");
644:
645: if ((tmp = p.getName()) != null) {
646: b.append(tmp);
647: }
648: b.append("|");
649:
650: if ((tmp = p.getActions()) != null) {
651: b.append(tmp);
652: }
653: return b.toString();
654: }
655:
656: static String[] parsePermission(String s) {
657: StringTokenizer st = new StringTokenizer(s, "|");
658: int len = st.countTokens();
659: if (len < 2 || len > 3)
660: return null;
661: String[] res = new String[len];
662: for (int i = 0; i < len; i++) {
663: res[i] = st.nextToken();
664: }
665: return res;
666: }
667:
668: }
|