added Confirmation.java
authoradam <adam@megacz.com>
Tue, 3 Aug 2004 07:23:16 +0000 (07:23 +0000)
committeradam <adam@megacz.com>
Tue, 3 Aug 2004 07:23:16 +0000 (07:23 +0000)
darcs-hash:20040803072316-5007d-909e0671698ce01c8a5ca0bca905ea8c8ef9cf8a.gz

src/org/ibex/mail/Confirmation.java [new file with mode: 0644]

diff --git a/src/org/ibex/mail/Confirmation.java b/src/org/ibex/mail/Confirmation.java
new file mode 100644 (file)
index 0000000..dc1cf3e
--- /dev/null
@@ -0,0 +1,136 @@
+package org.ibex.mail;
+import java.lang.reflect.*;
+import org.prevayler.*;
+import org.ibex.crypto.*;
+import org.ibex.util.*;
+import org.ibex.mail.protocol.*;
+import org.ibex.io.*;
+import java.util.*;
+import java.util.zip.*;
+import java.net.*;
+import java.io.*;
+
+/**
+ *  A convenient way to verify that the agent requesting an action
+ *  owns a particular email address.  Extend this class; the
+ *  Transaction.executeOn() method will be invoked when the user
+ *  clicks through.
+ */
+public abstract class Confirmation implements Externalizable {
+
+    public static final long serialVersionUID = 0x981879f18a11ffeeL;
+    public static final Address FROM = Address.parse("adam@megacz.com"); // FIXME
+
+    public    transient Address who         = null;
+    public              long    expiration;
+    public     abstract String  getDescription();
+
+    protected Confirmation(Address who, long expiration) { this.who = who; this.expiration = expiration; }
+
+    public void readExternal(ObjectInput s) throws IOException {
+        try {
+            int numfields = s.readInt();
+            Class c = this.getClass();
+            for(int i=0; i<numfields; i++) {
+                String name = s.readUTF();
+                Field f = c.getField(name);
+                Class c2 = f.getType();
+                if (c2 == Boolean.TYPE) f.setBoolean(this, s.readBoolean());
+                else if (c2 == Byte.TYPE) f.setByte(this, s.readByte());
+                else if (c2 == Long.TYPE) f.setLong(this, s.readLong());
+                else if (c2 == Integer.TYPE) f.setInt(this, s.readInt());
+                else if (c2 == Character.TYPE) f.setChar(this, s.readChar());
+                else if (c2 == Short.TYPE) f.setShort(this, s.readShort());
+                else if (c2 == Float.TYPE) f.setFloat(this, s.readFloat());
+                else if (c2 == Double.TYPE) f.setDouble(this, s.readDouble());
+                else if (c2 == String.class) f.set(this, s.readObject());
+                else f.set(this, s.readObject());
+            }
+        } catch (Exception e) {
+            Log.error(this, e);
+        }
+    }
+
+    public void writeExternal(ObjectOutput s) throws IOException {
+        try {
+            Class c = this.getClass();
+            Field[] fields = c.getFields();
+            int numfields = 0;
+            for(int i=0; i<fields.length; i++) {
+                Field f = fields[i];
+                if ((f.getModifiers() & (Modifier.STATIC | Modifier.TRANSIENT)) != 0) continue;
+                numfields++;
+            }
+            s.writeInt(numfields);
+            for(int i=0; i<fields.length; i++) {
+                Field f = fields[i];
+                if ((f.getModifiers() & (Modifier.STATIC | Modifier.TRANSIENT)) != 0) continue;
+                s.writeUTF(fields[i].getName());
+                Class c2 = f.getType();
+                if (c2 == Boolean.TYPE) s.writeBoolean(f.getBoolean(this));
+                else if (c2 == Byte.TYPE) s.writeByte(f.getByte(this));
+                else if (c2 == Long.TYPE) s.writeLong(f.getLong(this));
+                else if (c2 == Integer.TYPE) s.writeInt(f.getInt(this));
+                else if (c2 == Character.TYPE) s.writeChar(f.getChar(this));
+                else if (c2 == Short.TYPE) s.writeShort(f.getShort(this));
+                else if (c2 == Float.TYPE) s.writeFloat(f.getFloat(this));
+                else if (c2 == Double.TYPE) s.writeDouble(f.getDouble(this));
+                else if (c2 == String.class) s.writeUTF((String)f.get(this));
+                else s.writeObject(f.get(this));
+            }
+        } catch (Exception e) {
+            Log.error(this, e);
+        }
+    }
+
+    public void signAndSend(long secret) throws IOException, Message.Malformed {
+        SMTP.Outgoing.accept(new Message(new Stream("From: "    + FROM + "\r\n" +
+                                                    "To: "      + who.toString(true) + "\r\n" +
+                                                    "Subject: confirm " + getDescription() + "\r\n" +
+                                                    "\r\n" +
+                                                    "Please click the link below to " + getDescription() + "\r\n" +
+                                                    sign(secret)),
+                                         new Message.Envelope(FROM, who, new Date())
+                                         )
+                             );
+    }
+
+    public String sign(long secret) throws IOException {
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        ObjectOutputStream oos = new ObjectOutputStream(new DeflaterOutputStream(os));
+        oos.writeObject(this);
+        oos.flush();
+        oos.close();
+        byte[] b = os.toByteArray();
+        StringBuffer sb = new StringBuffer(new String(Base64.encode(b)));
+        sb.append('.');
+        SHA1 sha1 = new SHA1();
+        sha1.update(b, 0, b.length);
+        b = new byte[sha1.getDigestSize()];
+        sha1.doFinal(b, 0);
+        sb.append(new String(Base64.encode(b)));
+        return sb.toString();
+    }
+
+    public static Confirmation decode(String encoded, long secret) {
+        try {
+            // FIXME: not prevayler-safe!
+            String payload = encoded.substring(0, encoded.indexOf('.'));
+            ObjectInputStream ois = new ObjectInputStream(new InflaterInputStream(new Base64.InputStream(payload)));
+            Confirmation cve = (Confirmation)ois.readObject();
+            if (!cve.sign(secret).equals(encoded)) throw new InvalidSignature();
+            if (System.currentTimeMillis() > cve.expiration) throw new Expired();
+            return cve;
+        } catch (ClassNotFoundException e) {
+           Log.error(Confirmation.class, e);
+           throw new InvalidSignature();
+        } catch (IOException e) { 
+           Log.error(Confirmation.class, e);
+           throw new InvalidSignature();
+        }
+    }
+
+    public static class Exn extends RuntimeException { }
+    public static class Expired extends Exn { }
+    public static class InvalidSignature extends Exn { }
+}