001: /*
002:
003: This software is OSI Certified Open Source Software.
004: OSI Certified is a certification mark of the Open Source Initiative.
005:
006: The license (Mozilla version 1.0) can be read at the MMBase site.
007: See http://www.MMBase.org/license
008:
009: */
010: package org.mmbase.security.classsecurity;
011:
012: import java.util.*;
013: import java.util.regex.Pattern;
014: import java.net.URL;
015: import java.net.URLConnection;
016:
017: import org.mmbase.security.SecurityException;
018: import org.mmbase.security.MMBaseCopConfig;
019: import org.mmbase.util.*;
020: import org.mmbase.util.logging.*;
021: import org.mmbase.util.xml.DocumentReader;
022: import org.w3c.dom.*;
023: import org.xml.sax.InputSource;
024:
025: /**
026: * Provides the static utility methods to authenticate by class. Default a file
027: * security/<classauthentication.xml> is used for this. If using the wrapper authentication,
028: * its configuration file, contains this configuration.
029: *
030: * @author Michiel Meeuwissen
031: * @version $Id: ClassAuthentication.java,v 1.17 2007/02/11 19:45:04 nklasens Exp $
032: * @see ClassAuthenticationWrapper
033: * @since MMBase-1.8
034: */
035: public class ClassAuthentication {
036: private static final Logger log = Logging
037: .getLoggerInstance(ClassAuthentication.class);
038:
039: public static final String PUBLIC_ID_CLASSSECURITY_1_0 = "-//MMBase//DTD classsecurity config 1.0//EN";
040: public static final String DTD_CLASSSECURITY_1_0 = "classsecurity_1_0.dtd";
041: static {
042: XMLEntityResolver.registerPublicID(PUBLIC_ID_CLASSSECURITY_1_0,
043: DTD_CLASSSECURITY_1_0, ClassAuthentication.class);
044: }
045: private static List<Login> authenticatedClasses = null;
046:
047: static ResourceWatcher watcher = null;
048:
049: /**
050: * Stop watchin the config file, if there is watched one. This is needed when security
051: * configuration switched to ClassAuthenticationWrapper (which will not happen very often).
052: */
053:
054: static void stopWatching() {
055: if (watcher != null) {
056: watcher.exit();
057: }
058: }
059:
060: private ClassAuthentication() {
061: //Static Utility class
062: }
063:
064: /**
065: * Reads the configuration file and instantiates and loads the wrapped Authentication.
066: */
067: protected static synchronized void load(String configFile)
068: throws SecurityException {
069: List<URL> resourceList = MMBaseCopConfig.securityLoader
070: .getResourceList(configFile);
071: log.info("Loading " + configFile + "( " + resourceList + ")");
072: authenticatedClasses = new ArrayList<Login>();
073: ListIterator<URL> it = resourceList.listIterator();
074: while (it.hasNext())
075: it.next();
076: while (it.hasPrevious()) {
077: URL u = it.previous();
078: try {
079: URLConnection con = u.openConnection();
080: if (!con.getDoInput())
081: continue;
082: InputSource in = new InputSource(con.getInputStream());
083: Document document = DocumentReader.getDocumentBuilder(
084: true, // validate aggresively, because no further error-handling will be done
085: new XMLErrorHandler(false, 0), // don't log, throw exception if not valid, otherwise big chance on NPE and so on
086: new XMLEntityResolver(true,
087: ClassAuthentication.class) // validate
088: ).parse(in);
089:
090: NodeList authenticates = document
091: .getElementsByTagName("authenticate");
092:
093: for (int i = 0; i < authenticates.getLength(); i++) {
094: Node node = authenticates.item(i);
095: String clazz = node.getAttributes().getNamedItem(
096: "class").getNodeValue();
097: String method = node.getAttributes().getNamedItem(
098: "method").getNodeValue();
099: Node property = node.getFirstChild();
100: Map<String, String> map = new HashMap<String, String>();
101: while (property != null) {
102: if (property instanceof Element
103: && property.getNodeName().equals(
104: "property")) {
105: String name = property.getAttributes()
106: .getNamedItem("name")
107: .getNodeValue();
108: String value = property.getAttributes()
109: .getNamedItem("value")
110: .getNodeValue();
111: map.put(name, value);
112: }
113: property = property.getNextSibling();
114: }
115: authenticatedClasses.add(new Login(Pattern
116: .compile(clazz), method, Collections
117: .unmodifiableMap(map)));
118: }
119: } catch (Exception e) {
120: log.error(u + " " + e.getMessage(), e);
121: }
122: }
123:
124: { // last fall back, everybody may get the 'anonymous' cloud.
125: Map<String, String> map = new HashMap<String, String>();
126: map.put("rank", "anonymous");
127: authenticatedClasses.add(new Login(Pattern.compile(".*"),
128: "class", Collections.unmodifiableMap(map)));
129: }
130:
131: log.service("Class authentication: " + authenticatedClasses);
132:
133: }
134:
135: /**
136: * Checks wether the (indirectly) calling class is authenticated by the
137: * ClassAuthenticationWrapper (using a stack trace). This method can be called from an
138: * Authentication implementation, e.g. to implement the 'class' application itself (if the
139: * authentication implementation does understand the concept itself, then passwords can be
140: * avoided in the wrappers' configuration file).
141: *
142: * @param application Only checks this 'authentication application'. Can be <code>null</code> to
143: * check for every application.
144: * @return A Login object if yes, <code>null</code> if not.
145: */
146: public static Login classCheck(String application) {
147: if (authenticatedClasses == null) {
148: synchronized (ClassAuthentication.class) { // if load is running this locks
149: if (authenticatedClasses == null) { // if locked, load was running and this now skips, so load is not called twice.
150: String configFile = "classauthentication.xml";
151: load(configFile);
152: watcher = new ResourceWatcher(
153: MMBaseCopConfig.securityLoader) {
154: public void onChange(String resource) {
155: load(resource);
156: }
157: };
158: watcher.add(configFile);
159: watcher.start();
160: }
161: }
162: }
163: if (log.isDebugEnabled()) {
164: log.trace("Class authenticating (" + authenticatedClasses
165: + ")");
166: }
167: Throwable t = new Throwable();
168: StackTraceElement[] stack = t.getStackTrace();
169:
170: for (Login n : authenticatedClasses) {
171: if (application == null
172: || application.equals(n.application)) {
173: Pattern p = n.classPattern;
174: for (StackTraceElement element : stack) {
175: String className = element.getClassName();
176: if (className.startsWith("org.mmbase.security."))
177: continue;
178: if (className
179: .startsWith("org.mmbase.bridge.implementation."))
180: continue;
181: log.trace("Checking " + className);
182: if (p.matcher(className).matches()) {
183: if (log.isDebugEnabled()) {
184: log.debug("" + className + " matches! ->"
185: + n + " " + n.getMap());
186: }
187: return n;
188: }
189: }
190: }
191: }
192: if (log.isDebugEnabled()) {
193: log.debug("Failed to authenticate " + Arrays.asList(stack)
194: + " with " + authenticatedClasses);
195: }
196: return null;
197: }
198:
199: /**
200: * A structure to hold the login information.
201: */
202: public static class Login {
203: Pattern classPattern;
204: String application;
205: Map<String, String> map;
206:
207: Login(Pattern p, String a, Map<String, String> m) {
208: classPattern = p;
209: application = a;
210: map = m;
211: }
212:
213: public Map<String, String> getMap() {
214: return map;
215: }
216:
217: public String toString() {
218: return classPattern.pattern()
219: + (application.equals("class") ? "" : ": "
220: + application);
221: }
222: }
223:
224: }
|