CALL FOR:
Authentication Wrapper based on Class

Called by: Michiel Meeuwissen

START OF CALL: 2004-05-13 16:45
END OF CALL:   2003-05-16 16:45


Total tally on this call : +6


YEA (6) : Eduard Witteveen, Daniel Ockeloen, Kees Jongenburger, Pierre van Rooden, Andre van Toly, Rico Jansen

ABSTAIN (0) :

NAY (0) :

VETO (0) :

No votes, assumed abstained (11): Jaco de Groot, Marcel Maatkamp, Johannes Verelst, Rob Vermeulen, Nico Klasens, Rob van Maris, Gerard van Enk, Mark Huijser, Ernst Bunders


Vote result:
Undecided.
While majority of votes indicate success, since the number of votes on this call is very low, and there was discussion regarding implementation details, I will extend the call till monday 19 April.
Note that in general, hacks are votes on 'as is'. If implementation details change this should be made clear, allowing people to adapt their votes. You do NOT vote on an abstract idea - you vote for inclusion of the code as included in the Hack.
See implementation below.




Michiel Meeuwissen wrote:
Normally, when we want to implement a piece of MMBase functionality (e.g.
some scheduled job), we want to use 'bridge', because its much cleaner
interface. But to use bridge, you need to be logged on. So not seldomly, we
see code to acquire a Cloud, where authentication credententials are
hard-coded.

This is a undesirable situation, for which I wanted a fix.

The idea is that also originating classes could be trustworthy. So I want to
add an extra class to the security framework, which I have called
'ClassAuthenticationWrapper'. This class wraps another Authentication
implemementation, and has it's own XML, from which an example follows:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE security PUBLIC "-//MMBase//DTD classsecurity config 1.0//EN"
"http://www.mmbase.org/dtd/classsecurity_1_0.dtd";>
<security>

<authentication class="org.mmbase.security.implementation.cloudcontext.Authenticate" url="" />

  <authenticate class="org\.mmbase\..*" method="name/password">
    <property name="username" value="foo" />
    <property name="password" value="bar" />
  </authenticate>

</security>


So, this sais, that it is wrapping cloud context security authtentication and that all org.mmbase.* classes are trusted with the 'foo' user (using 'name/password')

In a class implemetned in org.mmbase.* I could now get a Cloud object like
this:

Cloud cloud = ContextProvider.getDefaultCloudContext().getCloud("mmbase", "class", null);

And I will get a Cloud where the user 'foo' is logged in. I did not have to
use a password. The Authentication Wrapper uses the stack trace to check if I
indeed am implementating in org.mmbase.

The advantage is now that the username/password is not hard-coded. It is
still in the XML though, so it is not quite perfect yet.

So, therefore I made the check-code public so it can also be called from
other Authentication implemetantions.

E.g. I have changed 'cloudcontext.Authenticate' so that it can recognize the
login method 'class' itself. It then calls the code from
ClassAuthenticationWrapper to check the stacktrace and the configuration.

Using that, the XML can be simplified to:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE security PUBLIC "-//MMBase//DTD classsecurity config 1.0//EN"
"http://www.mmbase.org/dtd/classsecurity_1_0.dtd";>
<security> <authentication class="org.mmbase.security.implementation.cloudcontext.Authenticate" url="" />


  <authenticate class="org\.mmbase\..*" method="class">
    <property name="username" value="foo" />
  </authenticate>

</security>

So, I request to instantiate a User object for the User 'foo' for all
org.mmbase classes which request authentication by 'class'. The password can
now be ignored.

If the calling class is matched by more than one of the 'authentice'
entries, the first match is used.


I want to propose to add ClassAuthenticationWrapper to org.mmbase.security.classsecurity. ALso, I want to change every Authentication implemetnation for which this is possible to recognize the 'class' method (I've tested this only for cloud context security now, but I think it is simple for all other implemtnation too). I would also add an extension of org.mmbase.security.SecurityException: UnknownAuthenticationMethodException to handle this stuff a bit gracefully.

The wrapper is currently in CVS speeltuin/mihxil/classecurity, but I attach
its implementation too.


START OF CALL: 2004-05-13 END OF CALL: 2003-05-16

 [_] +1 (YES)
 [_] +0 (ABSTAIN )
 [_] -1 (NO), because :
 [_] VETO, because:


Michiel


--
Michiel Meeuwissen |
Mediacentrum 140 H'sum | +31 (0)35 6772979 | I hate computers
nl_NL eo_XX en_US |
mihxil' |
[] () |



------------------------------------------------------------------------


/*

This software is OSI Certified Open Source Software.
OSI Certified is a certification mark of the Open Source Initiative.

The license (Mozilla version 1.0) can be read at the MMBase site.
See http://www.MMBase.org/license

*/
package org.mmbase.security.classsecurity;

import org.mmbase.security.SecurityException;
import org.mmbase.security.*;


import org.mmbase.util.logging.*; import org.mmbase.util.*; import org.mmbase.util.xml.*;


import java.util.*; import java.util.regex.*; import java.io.*; import org.w3c.dom.*; import org.w3c.dom.NodeList; import org.w3c.dom.Node; import org.w3c.dom.traversal.NodeIterator; import org.xml.sax.*;


/** * ClassAuthenticationWrapper wraps another Authentication implemention, and adds an extra * configuration file. In this configuration file the wrapped Authentication can be specified (and * <em>its</em> configuration file if it needs one). Besides that, also authentication credentials * can be linked to classes in this XML configuration file. * * @author Michiel Meeuwissen * @version $Id: ClassAuthenticationWrapper.java,v 1.2 2004/04/01 22:48:17 michiel Exp $ */ public class ClassAuthenticationWrapper extends Authentication { private static final Logger log = Logging.getLoggerInstance(ClassAuthenticationWrapper.class);

public static final String PUBLIC_ID_CLASSSECURITY_1_0 = "-//MMBase//DTD classsecurity config 1.0//EN";
public static final String DTD_CLASSSECURITY_1_0 = "classsecurity_1_0.dtd";
static {
XMLEntityResolver.registerPublicID(PUBLIC_ID_CLASSSECURITY_1_0, DTD_CLASSSECURITY_1_0, ClassAuthenticationWrapper.class);
}
private Authentication wrappedAuthentication;
private static List authenticatedClasses = new ArrayList();




    /**
     * Instantiates an Authentication implementation
     */
    private Authentication getAuthenticationInstance(String className) throws 
SecurityException {
        Authentication result;
        try {
            Class classType = Class.forName(className);
            Object o = classType.newInstance();
            result = (Authentication) o;
        } catch(ClassNotFoundException cnfe) {
            throw new SecurityException(cnfe);
        } catch(IllegalAccessException iae) {
            throw new SecurityException(iae);
        } catch(InstantiationException ie) {
            throw new SecurityException(ie);
        }
        return result;
    }

/**
* [EMAIL PROTECTED]
* Reads the configuration file and instantiates and loads the wrapped Authentication.
*/
protected void load() throws SecurityException {
try {
InputSource in = new InputSource(new FileInputStream(configFile));
Document document = DocumentReader.getDocumentBuilder(
true, // validate aggresively, because no further error-handling will be done
new XMLErrorHandler(false, 0), // don't log, throw exception if not valid, otherwise big chance on NPE and so on
new XMLEntityResolver(true, getClass()) // validate
).parse(in);
Node authentication = document.getElementsByTagName("authentication").item(0);
String wrappedClass = authentication.getAttributes().getNamedItem("class").getNodeValue();
String wrappedUrl = authentication.getAttributes().getNamedItem("url").getNodeValue();
wrappedAuthentication = getAuthenticationInstance(wrappedClass);
wrappedAuthentication.load(manager, fileWatcher, wrappedUrl);


NodeList authenticates = document.getElementsByTagName("authenticate");
for (int i = 0; i < authenticates.getLength(); i ++) {
Node node = authenticates.item(i);
String clazz = node.getAttributes().getNamedItem("class").getNodeValue();
String method = node.getAttributes().getNamedItem("method").getNodeValue();


Node property = node.getFirstChild();
Map map = new HashMap();
while (property != null) {
if (property instanceof Element && property.getNodeName().equals("property")) {
String name = property.getAttributes().getNamedItem("name").getNodeValue();
String value = property.getAttributes().getNamedItem("value").getNodeValue();
map.put(name, value);
}
property = property.getNextSibling();
}
authenticatedClasses.add(new Login(Pattern.compile(clazz), method, map));
}


        } catch (Exception fnfe) {
            throw new SecurityException(fnfe);
        }



}

/**
* Checks wether the (indirectly) calling class is authenticated by the
* ClassAuthenticationWrapper (using a stack trace). This method can be called from an
* Authentication implementation, e.g. to implement the 'class' application itself (if the
* authentication implementation does understand the concept itself, then passwords can be
* avoided in the wrappers' configuration file).
*
* @param application Only checks this 'authentication application'. Can be <code>null</code> to
* check for every application.
* @returns A Login object if yes, <code>null</code> if not.
*/
public static Login classCheck(String application) {
Throwable t = new Throwable();
StackTraceElement[] stack = t.getStackTrace();
Iterator i = authenticatedClasses.iterator();


        while(i.hasNext()) {
            Login n = (Login) i.next();
            if (application == null || application.equals(n.application)) {
                Pattern p = n.classPattern;
                for (int j = 0; j < stack.length; j++) {
                    String className = stack[j].getClassName();
                    if (className.startsWith("org.mmbase.security.")) continue;
                    if (className.startsWith("org.mmbase.bridge.implementation.")) 
continue;
                    log.trace("Checking " + className);
                    if (p.matcher(className).matches()) {
                        log.debug("" + className + " matches!");
                        return n;
                    }
                }
            }
        }
        return null;
    }

/**
* logs-in using the first match on the class from the configuration file.
* @param loginInfo If there are possible credentials already, they can be in this map. The new
* one will be added. If it is null, a new Map is instantiated.
* @param parameters Required by the login method of Authentication. I think noone ever uses it.
*/
protected UserContext login(Map loginInfo, Object[] parameters) throws SecurityException {
Login l = classCheck(null);
if (l != null) {
if (loginInfo == null) loginInfo = new HashMap();
loginInfo.putAll(l.map);
return wrappedAuthentication.login(l.application, loginInfo, parameters);
} return null;
}


    /**
     * [EMAIL PROTECTED]
     */
    public UserContext login(String application, Map loginInfo, Object[] parameters) 
throws SecurityException {

// first try 'cleanly':
try {
return wrappedAuthentication.login(application, loginInfo, parameters);
} catch (UnknownAuthenticationMethodException uam) { // no luck
return login(loginInfo, parameters); } catch (SecurityException se) { // perhaps not recognized log.warn("Authentication did not succeed " + se.getMessage() + " trying self");
return login(loginInfo, parameters);
}
}


    /**
     * [EMAIL PROTECTED]
     */
    public boolean isValid(UserContext userContext) throws SecurityException {
        return wrappedAuthentication.isValid(userContext);
    }

    /**
     * A structure to hold the login information.
     */
    public class  Login {
        Pattern classPattern;
        String application;
        Map    map;
        Login(Pattern p , String a, Map m) {
            classPattern = p;
            application = a;
            map = m;
        }

        public Map getMap() {
            return map;
        }
        public String toString() {
            return application;
        }
    }

}


--
Pierre van Rooden
Mediapark, C 107 tel. +31 (0)35 6772815
"Never summon anything bigger than your head."




Reply via email to