1 // Copyright 2000-2005 the Contributors, as shown in the revision logs.
2 // Licensed under the Apache Public Source License 2.0 ("the License").
3 // You may not use this file except in compliance with the License.
6 import java.lang.reflect.*;
7 //import org.prevayler.*;
8 import org.ibex.crypto.*;
9 import org.ibex.util.*;
10 import org.ibex.mail.protocol.*;
13 import java.util.zip.*;
18 * A convenient way to verify that the agent requesting an action
19 * owns a particular email address. Extend this class; the
20 * Transaction.executeOn() method will be invoked when the user
23 public abstract class Confirmation implements Externalizable {
25 public static final long serialVersionUID = 0x981879f18a11ffeeL;
27 public static int master_serial = 0;
29 public transient Address who = null;
30 public long expiration;
31 public abstract String getDescription();
32 public abstract String getURL(String tail);
35 private static HashMap<Integer,Confirmation> all = new HashMap<Integer,Confirmation>();
37 public static Confirmation get(int serial) {
38 return all.get(serial);
41 protected Confirmation(Address who, long expiration) {
43 this.expiration = expiration;
44 this.serial = master_serial++;
45 all.put(serial, this);
48 public void readExternal(ObjectInput s) throws IOException {
50 int numfields = s.readInt();
51 Class c = this.getClass();
52 for(int i=0; i<numfields; i++) {
53 String name = s.readUTF();
54 Field f = c.getField(name);
55 Class c2 = f.getType();
56 if (c2 == Boolean.TYPE) f.setBoolean(this, s.readBoolean());
57 else if (c2 == Byte.TYPE) f.setByte(this, s.readByte());
58 else if (c2 == Long.TYPE) f.setLong(this, s.readLong());
59 else if (c2 == Integer.TYPE) f.setInt(this, s.readInt());
60 else if (c2 == Character.TYPE) f.setChar(this, s.readChar());
61 else if (c2 == Short.TYPE) f.setShort(this, s.readShort());
62 else if (c2 == Float.TYPE) f.setFloat(this, s.readFloat());
63 else if (c2 == Double.TYPE) f.setDouble(this, s.readDouble());
64 else if (c2 == String.class) f.set(this, s.readObject());
65 else f.set(this, s.readObject());
67 } catch (Exception e) {
72 public void writeExternal(ObjectOutput s) throws IOException {
74 Class c = this.getClass();
75 Field[] fields = c.getFields();
77 for(int i=0; i<fields.length; i++) {
79 if ((f.getModifiers() & (Modifier.STATIC | Modifier.TRANSIENT)) != 0) continue;
82 s.writeInt(numfields);
83 for(int i=0; i<fields.length; i++) {
85 if ((f.getModifiers() & (Modifier.STATIC | Modifier.TRANSIENT)) != 0) continue;
86 s.writeUTF(fields[i].getName());
87 Class c2 = f.getType();
88 if (c2 == Boolean.TYPE) s.writeBoolean(f.getBoolean(this));
89 else if (c2 == Byte.TYPE) s.writeByte(f.getByte(this));
90 else if (c2 == Long.TYPE) s.writeLong(f.getLong(this));
91 else if (c2 == Integer.TYPE) s.writeInt(f.getInt(this));
92 else if (c2 == Character.TYPE) s.writeChar(f.getChar(this));
93 else if (c2 == Short.TYPE) s.writeShort(f.getShort(this));
94 else if (c2 == Float.TYPE) s.writeFloat(f.getFloat(this));
95 else if (c2 == Double.TYPE) s.writeDouble(f.getDouble(this));
96 else if (c2 == String.class) s.writeUTF((String)f.get(this));
97 else s.writeObject(f.get(this));
99 } catch (Exception e) {
104 public void signAndSend(Address sender, long secret, Date now) throws IOException, Message.Malformed {
106 Headers h = new Headers(new String[] {
107 "From", sender.toString(true),
108 "To", who.toString(true),
109 "Message-Id", Message.generateFreshMessageId(),
110 "Date", new Date()+"" /*FIXME!!!*/,
111 "Subject", "confirm " + getDescription()
114 Fountain fountain = Fountain.Util.create("Please click the link below to " +
115 getDescription() + "\r\n" +
116 getURL(sign(secret)));
117 Message m = Message.newMessageFromHeadersAndBody(h, fountain, sender, who);
118 SMTP.Outgoing.enqueue(m);
121 public String sign(long secret) throws IOException {
122 ByteArrayOutputStream os = new ByteArrayOutputStream();
123 ObjectOutputStream oos = new ObjectOutputStream(new DeflaterOutputStream(os));
124 oos.writeObject(this);
127 byte[] b = os.toByteArray();
128 StringBuffer sb = new StringBuffer();
130 //sb.append(new String(Encode.toBase64(b)));
132 SHA1 sha1 = new SHA1();
133 sha1.update(b, 0, b.length);
134 b = new byte[sha1.getDigestSize()];
136 sb.append(new String(Encode.toBase64(b)));
137 return sb.toString();
140 public static Confirmation decode(String encoded, long secret, Date now) {
142 String serial = encoded.substring(0, encoded.indexOf('.'));
143 Confirmation cve = get(Integer.parseInt(serial));
144 if (!cve.sign(secret).equals(encoded)) throw new InvalidSignature();
145 if (now.getTime() > cve.expiration) throw new Expired();
147 } catch (IOException e) {
148 Log.error(Confirmation.class, e);
149 throw new InvalidSignature();
153 public static class Exn extends RuntimeException { }
154 public static class Expired extends Exn { }
155 public static class InvalidSignature extends Exn { }