Monthly Archives: February 2010

Misc Pivot stuff

Padding choices

In wtkx, adding padding usually looks like: styles=”{padding:10}”. Nice if you want 10px all around, but you can also specify each side styles=”{padding:{top:1,left:2,bottom:3,right:4}}”.

Adding validators inline

Normally to add a validator to a component, you code something similar to

@WTKX
private TextInput maxFileSize;
...
...
maxFileSize.setValidator(new IntRangeValidator(0, Integer.MAX_VALUE));

Seems like a bit of a waste? In WTKX, you can include any object that has a no-argument constructor. So we can inline the whole thing.

  1. Include the package to your object as part of the xml namespace (notice xmlns:text)
  2. Define the validator.
  3. attach it to your component

 
     
 
 
...
...
   
...
 

SASL Plain Server

For some reason SUN includes support for sasl PLAIN in their SaslClient, but not SaslServer. Here’s a quick hack to implement a plain sasl server. As per rfc4616.txt, there is only message, three UTF-8 strings, max length 255 delimited by null (\u0000) sent from the client. There is no server response.

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.sasl.AuthorizeCallback;
import javax.security.sasl.SaslException;
import javax.security.sasl.SaslServer;

/**
 * Simple sasl server to decode a
 * @author toaster
 */
public class PlainSaslServer implements SaslServer
{

    public boolean complete = false;
    private String authID = null;
    private CallbackHandler cbh;
    
    PlainSaslServer(CallbackHandler cbh)
    {
        this.cbh = cbh;
    }

    public String getMechanismName()
    {
        return SaslConstants.SASL_SERVICE;
    }

    public byte[] evaluateResponse(byte[] response) throws SaslException
    {
        String authnID = null;
        String authzID = null;
        String password = null;

        // Decode response as per rfc4616.txt
//       The formal grammar for the client message using Augmented BNF [ABNF]
//       follows.
//
//       message   = [authzid] UTF8NUL authcid UTF8NUL passwd
//       authcid   = 1*SAFE ; MUST accept up to 255 octets
//       authzid   = 1*SAFE ; MUST accept up to 255 octets
//       passwd    = 1*SAFE ; MUST accept up to 255 octets
//       UTF8NUL   = %x00 ; UTF-8 encoded NUL character
//
//       SAFE      = UTF1 / UTF2 / UTF3 / UTF4
//                   ;; any UTF-8 encoded Unicode character except NUL
//
//       UTF1      = %x01-7F ;; except NUL
//       UTF2      = %xC2-DF UTF0
//       UTF3      = %xE0 %xA0-BF UTF0 / %xE1-EC 2(UTF0) /
//                   %xED %x80-9F UTF0 / %xEE-EF 2(UTF0)
//       UTF4      = %xF0 %x90-BF 2(UTF0) / %xF1-F3 3(UTF0) /
//                   %xF4 %x80-8F 2(UTF0)
//       UTF0      = %x80-BF

        int start = 0;
        int end = 0;
        int elementIdx = 0;

        try
        {


            for (byte b : response)
            {
                if (b == '\u0000')
                {
                    // empty string, only authzid allows this
                    if (end - start == 0)
                    {
                        if (elementIdx != 0)
                        {
                           throw new SaslException("null auth data");
                        }

                    } // data, wa-hoo
                    else
                    {
                        String element = new String(response, start, end - start, "UTF-8");
                        start = end + 1;

                        switch (elementIdx)
                        {
                            case 0:
                                authzID = element;
                                break;
                            case 1:
                                authnID = element;
                                break;
                            default:
                                throw new SaslException("Unexpected data in packet");
                        }
                    }
                    elementIdx++;

                }
                end++;
            }

            if (start == end)
            {
                throw new SaslException("null auth data");
            }

            password = new String(response, start, end - start, "UTF-8");


        } catch (UnsupportedEncodingException e)
        {
            throw new SaslException("utf-8 encoding");
        }


        ExternalValidationCallback evc = new ExternalValidationCallback(authnID, password);
        AuthorizeCallback ac = new AuthorizeCallback(authnID, authzID);

        Callback[] cbList = new Callback[2];

        cbList[0] = evc;
        cbList[1] = ac;

        try
        {
            if (password == null || authnID == null)
            {
                throw new SaslException("null auth data");
            }

            cbh.handle(cbList);
            if (!evc.isValidated())
            {
                throw new SaslException("cannot validate password");
            }

            if (!ac.isAuthorized())
            {
                throw new SaslException("user not authorized");
            }

            complete = true;
            return null;

        } catch (UnsupportedCallbackException ex)
        {
            throw new SaslException("unsupported callback", ex);

        } catch (IOException e)
        {
            if (e instanceof SaslException)
            {
                throw (SaslException) e;
            }
            throw new SaslException("Callback error", e);
        }

    }

    public boolean isComplete()
    {
        return complete;
    }

    public String getAuthorizationID()
    {
        if (!complete)
        {
            throw new IllegalStateException("not complete");
        }
        return authID;
    }

    public byte[] unwrap(byte[] incoming, int offset, int len)
    {
        if (!complete)
        {
            throw new IllegalStateException("not complete");
        }
        final byte[] result = new byte[len];
        System.arraycopy(incoming, offset, result, 0, len);
        return result;

    }

    public byte[] wrap(byte[] outgoing, int offset, int len)
    {
        if (!complete)
        {
            throw new IllegalStateException("not complete");
        }

        final byte[] result = new byte[len];
        System.arraycopy(outgoing, offset, result, 0, len);
        return result;

    }

    public Object getNegotiatedProperty(String propName)
    {
        return null;
    }

    public void dispose() throws SaslException
    {
    }
}

This also uses the following callback to validate the client’s username and password. The SaslServer will throw a SaslException if the isValidated returns false.

public class ExternalValidationCallback implements Callback
{

    private String username;
    private String password;
    private boolean validated = false;

    public ExternalValidationCallback(String username, String password)
    {
        this.username = username;
        this.password = password;
    }

    public String getPassword()
    {
        return password;
    }

    public String getUsername()
    {
        return username;
    }

    public boolean isValidated()
    {
        return validated;
    }

    public void setValidated(boolean validated)
    {
        this.validated = validated;
    }
}

Now after we have the new callback and server we need to register it as a provider w/in java. This requires that we create a server factory and simple security provider to register the factory. The server factory will need to check the sasl property POLICY_NOPLAINTEXT to see if its allowed to return the PLAIN server.

public class PlainSaslServerFactory implements SaslServerFactory
{

    private static String[] mechanisms =
    {
        "PLAIN"
    };

    public SaslServer createSaslServer(String mechanism, String protocol,
            String serverName,
            Map props, CallbackHandler cbh) throws SaslException
    {

        if (!mechanisms[0].equals(mechanism) || cbh == null)
            return null;
       return new PlainSaslServer(cbh);

    }

    public String[] getMechanismNames(Map props)
    {
        if ("true".equals(props.get(Sasl.POLICY_NOPLAINTEXT)))
            return new String[0];
        return mechanisms;
    }
}

Now, create the java security provider.

public final class MySecurityProvider extends Provider
{

    public MySecurityProvider()
    {
        super("My Provider", 1.0 , "Simple sasl plain provider");
        put("SaslServerFactory.PLAIN", "org.swap.provider.PlainSaslServerFactory");
    }

}

Putting it all together

// register provider
Security.addProvider(new MySecurityProvider());
// create callback
CallbackHandler myHandler = new CallbackHandler() {
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException
    {
 for (Callback cb : callbacks)
        {
            if (cb instanceof ExternalValidationCallback)
            {
                ExternalValidationCallback evc = (ExternalValidationCallback) cb;
                // Add your password validation, unless bob works for you
                evc.setValidated("bob".equals(evc.getUsername() && "password".equals()evc.getPassword());
            }
            else if (cb instanceof AuthorizeCallback)
            {
                AuthorizeCallback ac = (AuthorizeCallback) cb;
                // Add your test to see if client is authorized to use requested
                ac.setAuthorized(true);
            }
            else
            {
                throw new UnsupportedCallbackException(cb, "Unrecognized Callback");
            }
    }
};

SaslServer ss = Sasl.createSaslServer("PLAIN", "myProtocol", serverName, null, myHandler);
ss.evaluateResponse(clientPacket);

Pivot Tree List labels and lazy loading

Here’s how to lazy load a tree structure in pivot and supply nice(er) labels for all nodes.

1. Create a LabeledList that extends List

public class LabeledList extends ArrayList
{

private V label;

public LabeledList(V label)
{
    this.label = label;
}

public V getLabel()
{
    return label;
}

@Override
public boolean equals(Object o)
{
    return (o instanceof LabeledList && ((LabeledList) o).label.equals(label)
           && super.equals(o));
}

@Override
public int hashCode()
{
    int hash = 5;
    hash = 29 * hash + (this.label != null ? this.label.hashCode() : 0)
            + super.hashCode();
    return hash;
}
}
,v>

2. Create a simple renderer to display your new list

public class NodeTreeViewRenderer extends TreeViewNodeRenderer
{

 @Override
 public void render(Object o, Path path, int i, TreeView tv, boolean bln,
         boolean bln1, NodeCheckState ncs, boolean bln2, boolean bln3)
 {
     if (o != null && o instanceof LabeledList)
     {
         super.render(((LabeledList) o).getLabel(), path, i, tv,
                 bln, bln1, ncs, bln2, bln3);
     } else
     {
         super.render(o, path, i, tv, bln, bln1, ncs, bln2, bln3);
     }
 }
}

3. Put it together.

@WTKX TreeView myTree;
...
...
public void startup(Display display,
          Map map) throws Exception
  {
      ....
      List rootList = new arrayList();
      rootList.add(new LabeledList(new Date()));
      myTree.setData(rootList);
  },>,>

Now we have the problem of loading all the child nodes when the tree is expanded. This is handled by creating a TreeViewBranchListener and expanding LabeledList a little to track if it’s been loaded or not.

In LabeledList, add the following:

    private boolean loaded = false;

  public boolean isLoaded()
  {
      return loaded;
  }

  public void setLoaded(boolean loaded)
  {
      this.loaded = loaded;
  }

Now create the Listener

public class LazyLoadBranchListener implements TreeViewBranchListener
{
  public void branchExpanded(TreeView tv, Path path)
  {

      List currList = tv.getTreeData();

      for (Integer i : path)
      {
          currList = (List) currList.get(i);
      }

      LabeledList lList = (LabeledList) currList;

     if (!lList.isLoaded())
      {
          // load data for into lList. Add additional LabeledLists for directories or
          // straight objects for end nodes.
          // For long running loads, do the load in another thread
          lList.setLoaded(true);
      }
  }

  public void branchCollapsed(TreeView tv, Path path)
  {}
}

In your application class, add the new listener

    myTree.getTreeViewBranchListeners().add(new LazyLoadBranchListener());