incubator-heraldry-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ket...@apache.org
Subject svn commit: r463013 [4/8] - in /incubator/heraldry/libraries/java: ./ trunk/ trunk/META-INF/ trunk/bin/ trunk/bin/com/ trunk/bin/com/janrain/ trunk/bin/com/janrain/openid/ trunk/bin/com/janrain/openid/consumer/ trunk/bin/com/janrain/openid/store/ trunk...
Date Wed, 11 Oct 2006 22:33:11 GMT
Added: incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/Associator.java
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/Associator.java?view=auto&rev=463013
==============================================================================
--- incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/Associator.java (added)
+++ incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/Associator.java Wed Oct 11 15:33:04 2006
@@ -0,0 +1,369 @@
+/*
+ * Associator.java Created on February 8, 2006, 1:00 PM
+ */
+
+package com.janrain.openid.consumer;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.math.BigInteger;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.janrain.openid.DiffieHellman;
+import com.janrain.openid.Association;
+import com.janrain.openid.Util;
+import com.janrain.url.FetchResponse;
+import com.janrain.url.HTTPFetcher;
+
+/**
+ * This class contains the logic for creating new associations.
+ * 
+ * @author JanRain, Inc.
+ */
+public class Associator
+{
+    private static final String cls = "com.janrain.openid.consumer.Associator";
+    private static final Logger logger = Logger.getLogger(cls);
+    private static Set associationTypes = new HashSet();
+    private static Map sessionTypes = new HashMap();
+
+    static
+    {
+        associationTypes.add("HMAC-SHA1");
+
+        sessionTypes.put("", DefaultSessionHandler.class);
+        sessionTypes.put("DH-SHA1", DhSha1SessionHandler.class);
+    }
+
+    private static class AssociationBuilder
+    {
+        private boolean dirty = true;
+        private Association cached;
+        private String handle;
+        private byte [] secret;
+        private long lifetime;
+        private String assocType;
+        private String error;
+
+        public void setHandle(String handle)
+        {
+            dirty = true;
+            this.handle = handle;
+        }
+
+        public void setSecret(byte [] secret)
+        {
+            dirty = true;
+            this.secret = secret;
+        }
+
+        public void setLifetime(long lifetime)
+        {
+            dirty = true;
+            this.lifetime = lifetime;
+        }
+
+        public void setAssocType(String assocType)
+        {
+            dirty = true;
+            this.assocType = assocType;
+        }
+
+        public void setError(String error)
+        {
+            dirty = true;
+            this.error = error;
+        }
+
+        public Association getAssociation()
+        {
+            if (!dirty) return cached;
+
+            if (error != null)
+            {
+                throw new IllegalStateException(error);
+            }
+
+            cached = new Association(handle, secret, lifetime, assocType);
+            dirty = false;
+            return cached;
+        }
+    }
+
+    private abstract class SessionHandler
+    {
+        public SessionHandler()
+        {
+            // do nothing
+        }
+
+        public Map buildArgs()
+        {
+            Map args = new HashMap();
+
+            args.put("openid.mode", "associate");
+            args.put("openid.assoc_type", assocType);
+            args.put("openid.session_type", sessionType);
+
+            return args;
+        }
+
+        public AssociationBuilder parseResponse(Map response)
+        {
+
+            AssociationBuilder result = new AssociationBuilder();
+
+            String assocType = (String)response.get("assoc_type");
+            if (assocType == null || !associationTypes.contains(assocType))
+            {
+                logger.log(Level.INFO, "Unknown association type", assocType);
+                result.setError("Unknown association type: " + assocType);
+            }
+            result.setAssocType(assocType);
+
+            String assocHandle = (String)response.get("assoc_handle");
+            if (assocHandle == null)
+            {
+                String err = "No association handle provided";
+                logger.log(Level.INFO, err);
+                result.setError(err);
+            }
+            result.setHandle(assocHandle);
+
+            try
+            {
+                result.setLifetime(Long.parseLong((String)response
+                        .get("expires_in")));
+            }
+            catch (NullPointerException e)
+            {
+                String err = "No expires_in field provided";
+                logger.log(Level.INFO, err);
+                result.setError(err);
+            }
+            catch (NumberFormatException e)
+            {
+                String err = "expires_in field didn't contain an integer";
+                logger.log(Level.INFO, err);
+                result.setError(err);
+            }
+
+            return result;
+        }
+    }
+
+    private class DefaultSessionHandler extends SessionHandler
+    {
+        public DefaultSessionHandler()
+        {
+        }
+
+        public Map buildArgs()
+        {
+            return super.buildArgs();
+        }
+
+        public AssociationBuilder parseResponse(Map response)
+        {
+            AssociationBuilder result = super.parseResponse(response);
+
+            String encMacKey = (String)response.get("mac_key");
+            if (encMacKey == null)
+            {
+                String err = "mac_key field required and missing";
+                logger.log(Level.INFO, err);
+                result.setError(err);
+            }
+            result.setSecret(Util.fromBase64(encMacKey));
+
+            return result;
+        }
+    }
+
+    private class DhSha1SessionHandler extends SessionHandler
+    {
+        public DhSha1SessionHandler()
+        {
+        }
+
+        private DiffieHellman dh;
+
+        public Map buildArgs()
+        {
+            Map args = super.buildArgs();
+            dh = new DiffieHellman();
+
+            args.put("openid.dh_consumer_public", Util.numberToBase64(dh
+                    .getPublicKey()));
+
+            if (!dh.usesDefaults())
+            {
+                args.put("openid.dh_modulus", Util.numberToBase64(dh
+                        .getModulus()));
+            }
+
+            return args;
+        }
+
+        public AssociationBuilder parseResponse(Map response)
+        {
+            String sessionType = (String)response.get("session_type");
+            if (sessionType == null)
+            {
+                return new DefaultSessionHandler().parseResponse(response);
+            }
+
+            AssociationBuilder result = super.parseResponse(response);
+
+            if (!sessionType.equals("DH-SHA1"))
+            {
+                String err = "Unknown session type: " + sessionType;
+                logger.log(Level.INFO, err);
+                result.setError(err);
+                return result;
+            }
+
+            String serverPublic = (String)response.get("dh_server_public");
+            if (serverPublic == null)
+            {
+                String err = "Server public key absent";
+                logger.log(Level.INFO, err);
+                result.setError(err);
+                return result;
+            }
+
+            BigInteger spub = Util.base64ToNumber(serverPublic);
+
+            String b64MacKey = (String)response.get("enc_mac_key");
+            if (b64MacKey == null)
+            {
+                String err = "Server public key absent";
+                logger.log(Level.INFO, err);
+                result.setError(err);
+                return result;
+            }
+
+            byte [] secret = dh.xorSecret(spub, Util.fromBase64(b64MacKey));
+            if (secret == null)
+            {
+                String err = "Unable to calculate secret";
+                logger.log(Level.INFO, err);
+                result.setError(err);
+            }
+
+            result.setSecret(secret);
+            return result;
+        }
+    }
+
+    private String assocType;
+    private String sessionType;
+
+    public Associator(String associationType, String preferredSessionType)
+    {
+        if (!associationTypes.contains(associationType))
+        {
+            throw new IllegalArgumentException(
+                    "The only currently-supported association type is HMAC-SHA1");
+        }
+        assocType = associationType;
+
+        if (!sessionTypes.containsKey(preferredSessionType))
+        {
+            throw new IllegalArgumentException("Not a valid session type.");
+        }
+        sessionType = preferredSessionType;
+    }
+
+    public Associator()
+    {
+        this("HMAC-SHA1", "DH-SHA1");
+    }
+
+    public Association createAssociation(String serverUrl)
+    {
+        logger.entering(cls, "createAssociation", serverUrl);
+
+        HTTPFetcher fetcher = HTTPFetcher.getFetcher();
+        SessionHandler sess = getSessionHandler();
+        Map args = sess.buildArgs();
+
+        Map response = getServerResponse(fetcher, serverUrl, args);
+
+        if (response == null)
+        {
+            logger.exiting(cls, "createAssociation", null);
+            return null;
+        }
+
+        AssociationBuilder res = sess.parseResponse(response);
+
+        try
+        {
+            Association result = res.getAssociation();
+            logger.exiting(cls, "createAssociation", result);
+            return result;
+        }
+        catch (IllegalStateException e)
+        {
+            logger.log(Level.INFO, "Exception building Association", e);
+            logger.exiting(cls, "createAssociation", null);
+            return null;
+        }
+    }
+
+    private Map getServerResponse(HTTPFetcher fetcher, String serverUrl,
+            Map args)
+    {
+        logger.entering(cls, "getServerResponse", new Object[] {fetcher,
+                serverUrl, args});
+
+        String body = Util.encodeArgs(args);
+        FetchResponse fr = fetcher.fetch(serverUrl, body);
+        if (fr == null || fr.getStatusCode() != 200)
+        {
+            logger.log(Level.INFO,
+                    "Fetch failure attempting to make association", fr);
+            logger.exiting(cls, "getServerResponse", null);
+            return null;
+        }
+
+        return Util.parseKVForm(fr.getContent());
+    }
+
+    private SessionHandler getSessionHandler()
+    {
+        logger.entering(cls, "getSessionHandler");
+        Class c = (Class)sessionTypes.get(sessionType);
+        try
+        {
+            Constructor cons = c.getConstructors()[0];
+            Object [] initArgs = {this};
+            SessionHandler result = (SessionHandler)cons.newInstance(initArgs);
+            logger.exiting(cls, "getSessionHandler", result);
+            return result;
+        }
+        catch (InstantiationException e)
+        {
+            logger.log(Level.SEVERE,
+                    "Unexpected exception create a session handler.", e);
+        }
+        catch (IllegalAccessException e)
+        {
+            logger.log(Level.SEVERE,
+                    "Unexpected exception create a session handler.", e);
+        }
+        catch (InvocationTargetException e)
+        {
+            logger.log(Level.SEVERE,
+                    "Unexpected exception create a session handler.", e);
+        }
+        logger.exiting(cls, "getSessionHandler", null);
+        return null;
+    }
+}

Added: incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/AuthRequest.java
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/AuthRequest.java?view=auto&rev=463013
==============================================================================
--- incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/AuthRequest.java (added)
+++ incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/AuthRequest.java Wed Oct 11 15:33:04 2006
@@ -0,0 +1,167 @@
+package com.janrain.openid.consumer;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.janrain.openid.Association;
+import com.janrain.openid.Util;
+
+/**
+ * This class contains the intermediate status in an OpenID authentication
+ * request. The <code>begin</code> method in
+ * <code>com.janrain.openid.consumer.Consumer</code> returns an instance of
+ * this class. This class supports adding queries to the request before it gets
+ * sent for using OpenID extensions. It also gives access to some information
+ * about the claimed identity before it is used. The information available
+ * through the <code>getService</code> method allows you to determine the
+ * identity a user is claiming and the ID Server they are using before
+ * proceeding with the OpenID process. This can be used to black/white-list IDs
+ * or ID servers, or to use logic based on user-history to decide what, if any,
+ * extensions to use.
+ * 
+ * @author JanRain, Inc.
+ */
+public class AuthRequest
+{
+    private Association assoc;
+    private Token token;
+    private OpenIDService service;
+
+    private Map extraArgs = new HashMap();
+    private Map returnToArgs = new HashMap();
+
+    /**
+     * Create a new AuthRequest object
+     * 
+     * @param token
+     * @param assoc
+     * @param service
+     */
+    AuthRequest(Token token, Association assoc, OpenIDService service)
+    {
+        this.token = token;
+        this.assoc = assoc;
+        this.service = service;
+    }
+
+    /**
+     * @return the association that will be used in the request this generates
+     */
+    public Association getAssoc()
+    {
+        return assoc;
+    }
+
+    /**
+     * @return the extra query args that will be sent with this request
+     */
+    public Map getExtraArgs()
+    {
+        return extraArgs;
+    }
+
+    /**
+     * @return the extra query args that will be added to the return to URL
+     */
+    public Map getReturnToArgs()
+    {
+        return returnToArgs;
+    }
+
+    /**
+     * @return the information on the identity and server which will be used for
+     *         this request
+     */
+    public OpenIDService getService()
+    {
+        return service;
+    }
+
+    /**
+     * @return the token that will be used to store data between this request
+     *         and the return request
+     */
+    public Token getToken()
+    {
+        return token;
+    }
+
+    /**
+     * This method adds an extension argument to this request.
+     * 
+     * @param namespace
+     *            the namespace for the extension. For example, the simple
+     *            registration extension uses the namespace "sreg"
+     * @param key
+     *            the key within the extension namespace. For example, the
+     *            nickname field in the simple registration extension's key is
+     *            "nickname"
+     * @param value
+     *            the value to provide to the server for this argument
+     */
+    public void addExtensionArg(String namespace, String key, String value)
+    {
+        String name = "openid." + namespace + "." + key;
+        extraArgs.put(name, value);
+    }
+
+    /*
+     * Add a key-value pair to the return to args
+     */
+    void addReturnToArg(String key, String value)
+    {
+        returnToArgs.put(key, value);
+    }
+
+    /**
+     * This method returns a URL to redirect the user to in order to continue
+     * the authentication process.
+     * 
+     * @param trustRoot
+     *            this is the url the server will ask the user to approve. It
+     *            must be a parent url of the return to url
+     * @param returnTo
+     *            this is the url the server will send its response to. It needs
+     *            to handle the server's response
+     * @param immediate
+     *            <code>true</code> to use immediate mode, <false> for setup
+     *            mode
+     * @return a URL to direct the user to so the Authentication proceeds
+     */
+    public String redirectUrl(String trustRoot, String returnTo,
+            boolean immediate)
+    {
+        String mode = "checkid_" + (immediate ? "immediate" : "setup");
+        returnTo = Util.appendArgs(returnTo, returnToArgs);
+
+        Map redirArgs = new HashMap();
+        redirArgs.put("openid.mode", mode);
+        redirArgs.put("openid.identity", service.getDelegate());
+        redirArgs.put("openid.return_to", returnTo);
+        redirArgs.put("openid.trust_root", trustRoot);
+
+        if (assoc != null)
+        {
+            redirArgs.put("openid.assoc_handle", assoc.getHandle());
+        }
+
+        redirArgs.putAll(extraArgs);
+        return Util.appendArgs(service.getServerUrl(), redirArgs);
+    }
+
+    /**
+     * Equivalent to calling redirectUrl((trustRoot, returnTo, false);
+     * 
+     * @param trustRoot
+     *            this is the url the server will ask the user to approve. It
+     *            must be a parent url of the return to url
+     * @param returnTo
+     *            this is the url the server will send its response to. It needs
+     *            to handle the server's response
+     * @return a URL to direct the user to so the Authentication proceeds
+     */
+    public String redirectUrl(String trustRoot, String returnTo)
+    {
+        return redirectUrl(trustRoot, returnTo, false);
+    }
+}

Added: incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/CancelResponse.java
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/CancelResponse.java?view=auto&rev=463013
==============================================================================
--- incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/CancelResponse.java (added)
+++ incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/CancelResponse.java Wed Oct 11 15:33:04 2006
@@ -0,0 +1,15 @@
+package com.janrain.openid.consumer;
+
+/**
+ * A response with a status of <code>StatusCode.CANCELLED</code>. This
+ * response indicates that the user cancelled their login request.
+ * 
+ * @author JanRain, Inc.
+ */
+public class CancelResponse extends Response
+{
+    public CancelResponse(String identityUrl)
+    {
+        super(StatusCode.CANCELLED, identityUrl);
+    }
+}

Added: incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/Consumer.java
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/Consumer.java?view=auto&rev=463013
==============================================================================
--- incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/Consumer.java (added)
+++ incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/Consumer.java Wed Oct 11 15:33:04 2006
@@ -0,0 +1,387 @@
+package com.janrain.openid.consumer;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import com.janrain.openid.Util;
+import com.janrain.openid.store.OpenIDStore;
+
+/**
+ * <p>
+ * This class is the main interface with the OpenID consumer library. The only
+ * part of the library which has to be used and isn't documented in full here is
+ * the store required to create an <code>Consumer</code> instance. See the
+ * constructor for more information on stores, see the
+ * <code>com.janrain.openid.store</code> package.
+ * </p>
+ * <h1>OVERVIEW</h1>
+ * <p>
+ * The OpenID identity verification process most commonly uses the following
+ * steps, as visible to the user of this library:
+ * </p>
+ * <ol>
+ * <li>The user enters their OpenID into a field on the consumer's site, and
+ * hits a login button.</li>
+ * <li>The consumer site discovers the user's OpenID server using the YADIS
+ * protocol.</li>
+ * <li>The consumer site sends the browser a redirect to the identity server.
+ * This is the authentication request as described in the OpenID specification.</li>
+ * <li> The identity server's site sends the browser a redirect back to the
+ * consumer site. This redirect contains the server's response to the
+ * authentication request.</li>
+ * </ol>
+ * <p>
+ * The most important part of the flow to note is the consumer's site must
+ * handle two separate HTTP requests in order to perform the full identity
+ * check.
+ * </p>
+ * <h1>LIBRARY DESIGN</h1>
+ * <p>
+ * This consumer library is designed with that flow in mind. The goal is to make
+ * it as easy as possible to perform the above steps securely.
+ * </p>
+ * <p>
+ * At a high level, there are two important parts in the consumer library. The
+ * first important part is this class, which contains the interface to actually
+ * use this library. The second is the
+ * <code>com.janrain.openid.store.OpenIDStore</code> class, which describes
+ * the interface to use if you need to create a custom method for storing the
+ * state this library needs to maintain between requests.
+ * </p>
+ * <h1>STORES AND DUMB MODE</h1>
+ * <p>
+ * OpenID is a protocol that works best when the consumer site is able to store
+ * some state. This is the normal mode of operation for the protocol, and is
+ * sometimes referred to as smart mode. There is also a fallback mode, known as
+ * dumb mode, which is available when the consumer site is not able to store
+ * state. This mode should be avoided when possible, as it leaves the
+ * implementation more vulnerable to replay attacks.
+ * </p>
+ * <p>
+ * The mode the library works in for normal operation is determined by the store
+ * that it is given. The store is an abstraction that handles the data that the
+ * consumer needs to manage between http requests in order to operate
+ * efficiently and securely.
+ * </p>
+ * <p>
+ * One store implementation is provided, and the interface is fully documented
+ * so that custom stores can be used as well. See the documentation for the
+ * <code>com.janrain.openid.store.OpenIDStore</code> class for more
+ * information on the interface for stores. The provided store is a thread-safe
+ * in-memory store that can be serialized on application shutdown. As such, it's
+ * ideal for single-JVM servers. Servers that are distributed over multiple JVMs
+ * will need custom store implementations.
+ * </p>
+ * <p>
+ * There is an additional concrete store provided that puts the system in dumb
+ * mode. This is not recommended, as it removes the library's ability to stop
+ * replay attacks reliably. It still uses time-based checking to make replay
+ * attacks only possible within a small window, but they remain possible within
+ * that window. This store should only be used if the consumer site has no way
+ * to retain data between requests at all.
+ * </p>
+ * <h1>IMMEDIATE MODE </h1>
+ * <p>
+ * In the flow described above, the user may need to confirm to the identity
+ * server that it's ok to authorize his or her identity. The server may draw
+ * pages asking for information from the user before it redirects the browser
+ * back to the consumer's site. This is generally transparent to the consumer
+ * site, so it is typically ignored as an implementation detail.
+ * </p>
+ * <p>
+ * There can be times, however, where the consumer site wants to get a response
+ * immediately. When this is the case, the consumer can put the library in
+ * immediate mode. In immediate mode, there is an extra response possible from
+ * the server, which is essentially the server reporting that it doesn't have
+ * enough information to answer the question yet. In addition to saying that,
+ * the identity server provides a URL to which the user can be sent to provide
+ * the needed information and let the server finish handling the original
+ * request.
+ * </p>
+ * <h1>USING THIS LIBRARY</h1>
+ * <p>
+ * Integrating this library into an application is usually a relatively
+ * straightforward process. The process should basically follow this plan:
+ * </p>
+ * <p>
+ * Add an OpenID login field somewhere on your site. When an OpenID is entered
+ * in that field and the form is submitted, it should make a request to the your
+ * site which includes that OpenID URL.
+ * </p>
+ * <p>
+ * First, the application should instantiate the <code>Consumer</code> class
+ * using the store of choice. In addition to a store, the constructor also takes
+ * a <code>Map</code> to use as per-user state storage. This <code>Map</code>
+ * should be stored in the user session, and is referred to as the session
+ * <code>Map</code>.
+ * </p>
+ * <p>
+ * Next, the application should call the <code>begin</code> method on the
+ * <code>Consumer</code> instance. This method takes the OpenID URL. The
+ * <code>begin</code> method returns an <code>AuthRequest</code> object.
+ * </p>
+ * <p>
+ * Next, the application should call the <code>redirectURL</code> method on
+ * the <code>AuthRequest</code> object. The parameter <code>returnTo</code>
+ * is the URL that the OpenID server will send the user back to after attempting
+ * to verify his or her identity. The <code>trustRoot</code> parameter is the
+ * URL (or URL pattern) that identifies your web site to the user when he or she
+ * is authorizing it. Send a redirect to the resulting URL to the user's
+ * browser.
+ * </p>
+ * <p>
+ * That's the first half of the authentication process. The second half of the
+ * process is done after the user's ID server sends the user's browser a
+ * redirect back to your site to complete their login.
+ * </p>
+ * <p>
+ * When that happens, the user will contact your site at the URL given as the
+ * <code>returnTo</code> URL to the <code>redirectURL</code> call made
+ * above. The request will have several query parameters added to the URL by the
+ * identity server as the information necessary to finish the request.
+ * </p>
+ * <p>
+ * Get an <code>Consumer</code> instance, and call its <code>complete</code>
+ * method, passing in all the received query arguments. There are multiple
+ * possible return types possible from that method. These indicate the whether
+ * or not the login was successful, and include any additional information
+ * appropriate for their type.
+ * </p>
+ * 
+ * @author JanRain, Inc.
+ */
+public class Consumer
+{
+    private static String sessionKeyPrefix = "_openid_consumer_";
+    private static String tokenName = "last_token";
+    private static String managerName = "manager";
+
+    private Map session;
+    private OpenIDStore store;
+    private GenericConsumer consumer;
+
+    /**
+     * Creates a new Consumer instance.
+     * 
+     * @param session
+     *            a <code>Map</code> that the application will preserve
+     *            between the current user's requests
+     * @param store
+     *            the store that will be used to keep this site's state
+     */
+    public Consumer(Map session, OpenIDStore store)
+    {
+        this.session = session;
+        this.store = store;
+        consumer = new GenericConsumer(this.store);
+    }
+
+    /**
+     * This method starts the OpenID authentication process.
+     * 
+     * @param userUrl
+     *            The identity string provided by the user. This method performs
+     *            transformations as defined by the OpenID specification on the
+     *            input value to normalize it. For instance, the value
+     *            <code>"example.com"</code> will converted to
+     *            <code>"http://example.com/"</code>, and then any redirects
+     *            the server may issue.
+     * @return An <code>AuthRequest</code> object containing information about
+     *         the OpenID request that is about to be made.
+     * @throws IOException
+     *             when the entered value isn't a fetchable URL, when the URL
+     *             doesn't support OpenID, or when all OpenID services for a
+     *             given URL have failed to work
+     */
+    public AuthRequest begin(String userUrl) throws IOException
+    {
+        String openidUrl = Util.normalizeUrl(userUrl);
+        DiscoveryManager dm = (DiscoveryManager)session.get(sessionKeyPrefix
+                + managerName);
+
+        if (dm == null)
+        {
+            dm = new DiscoveryManager(openidUrl);
+            session.put(sessionKeyPrefix + managerName, dm);
+        }
+
+        if (dm.isEmpty())
+        {
+            session.remove(sessionKeyPrefix + managerName);
+            throw new IOException("No OpenID services found at that URL");
+        }
+
+        if (!dm.hasNext())
+        {
+            session.remove(sessionKeyPrefix + managerName);
+            throw new IOException(
+                    "No usable OpenID services found for that URL");
+        }
+
+        return beginWithoutDiscovery(dm.getNext());
+    }
+
+    /**
+     * This method starts OpenID authentication without doing OpenID server
+     * discovery. This method is called by <code>begin</code> to do its
+     * post-discovery work. It is publicly visible for use when consumers want
+     * to do their own OpenID service discovery.
+     * 
+     * @param service
+     *            the service information needed by the library
+     * @return An <code>AuthRequest</code> object containing information about
+     *         the OpenID request that is about to be made.
+     */
+    public AuthRequest beginWithoutDiscovery(OpenIDService service)
+    {
+        AuthRequest result = consumer.begin(service);
+        session.put(sessionKeyPrefix + tokenName, result.getToken());
+        return result;
+    }
+
+    /**
+     * This method interpret's a server's response to an OpenID request.
+     * 
+     * @param query
+     *            a <code>Map</code> from query parameter name to query
+     *            parameter value, <code>String</code> to <code>String</code>.
+     *            As many applications using this library will be servlets, a
+     *            convenience function has been provided to convert between the
+     *            format <code>getParameterMap</code> provides and the format
+     *            this method expects. See <code>filterArgs</code>.
+     * @return a <code>Response</code> subclass containing the status of the
+     *         authentication request
+     */
+    public Response complete(Map query)
+    {
+        Token token = (Token)session.get(sessionKeyPrefix + tokenName);
+
+        Response result;
+        if (token == null)
+        {
+            result = new FailureResponse(null, "No session state found");
+        }
+        else
+        {
+            result = consumer.complete(query, token);
+            session.remove(sessionKeyPrefix + tokenName);
+        }
+
+        StatusCode status = result.getStatus();
+
+        String identityUrl = null;
+        if (status == StatusCode.SUCCESS)
+        {
+            SuccessResponse sr = (SuccessResponse)result;
+            identityUrl = sr.getIdentityUrl();
+        }
+        else if (status == StatusCode.CANCELLED)
+        {
+            CancelResponse cr = (CancelResponse)result;
+            identityUrl = cr.getIdentityUrl();
+        }
+
+        if (identityUrl != null)
+        {
+            session.remove(sessionKeyPrefix + managerName);
+        }
+
+        return result;
+    }
+
+    /**
+     * @param args
+     *            a <code>Map</code> with <code>String</code>s as keys and
+     *            <code>String []</code>s as values, like the
+     *            <code>Map</code>s returned in the servlet API.
+     * @return a <code>Map</code> with <code>String</code>s as keys and
+     *         <code>String</code>s as values, suitable for use in the
+     *         <code>complete</code> method.
+     */
+    public static Map filterArgs(Map args)
+    {
+        Map result = new HashMap();
+        Iterator it = args.entrySet().iterator();
+        while (it.hasNext())
+        {
+            Map.Entry e = (Map.Entry)it.next();
+            String k = (String)e.getKey();
+            String [] v = (String [])e.getValue();
+            result.put(k, v[0]);
+        }
+        return result;
+    }
+
+    /**
+     * @return a list of characters that will be used for nonces
+     */
+    public static char [] getNonceChars()
+    {
+        return GenericConsumer.getNonceChars();
+    }
+
+    /**
+     * @param nonceChars
+     *            the new list of nonce characters
+     */
+    public static void setNonceChars(char [] nonceChars)
+    {
+        GenericConsumer.setNonceChars(nonceChars);
+    }
+
+    /**
+     * @return the length of nonces
+     */
+    public static int getNonceLen()
+    {
+        return GenericConsumer.getNonceLen();
+    }
+
+    /**
+     * @param nonceLen
+     *            the new length of nonces
+     */
+    public static void setNonceLen(int nonceLen)
+    {
+        GenericConsumer.setNonceLen(nonceLen);
+    }
+
+    /**
+     * @return the number of seconds an authentication requst is valid for
+     */
+    public static long getTokenLifetime()
+    {
+        return GenericConsumer.getTokenLifetime();
+    }
+
+    /**
+     * @param tokenLifetime
+     *            the new number of seconds an authentication request is valid
+     *            for
+     */
+    public static void setTokenLifetime(long tokenLifetime)
+    {
+        GenericConsumer.setTokenLifetime(tokenLifetime);
+    }
+
+    /**
+     * @return the name of the URL parameter that will be used to store the
+     *         nonce
+     */
+    public static String getNonceName()
+    {
+        return GenericConsumer.getNonceName();
+    }
+
+    /**
+     * @param nonceName
+     *            the new name of the URL parameter that will be used to store
+     *            the nonce.
+     */
+    public static void setNonceName(String nonceName)
+    {
+        GenericConsumer.setNonceName(nonceName);
+    }
+}

Added: incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/DiscoveryManager.java
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/DiscoveryManager.java?view=auto&rev=463013
==============================================================================
--- incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/DiscoveryManager.java (added)
+++ incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/DiscoveryManager.java Wed Oct 11 15:33:04 2006
@@ -0,0 +1,198 @@
+package com.janrain.openid.consumer;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Logger;
+
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import com.janrain.url.FetchResponse;
+import com.janrain.url.HTTPFetcher;
+import com.janrain.yadis.DiscoveryFailure;
+import com.janrain.yadis.DiscoveryResult;
+import com.janrain.yadis.ServiceParser;
+import com.janrain.yadis.XRDS;
+import com.janrain.yadis.XRDSError;
+
+/**
+ * This class provides the consumer library with the logic to find a list of
+ * OpenID services for a URL.
+ * 
+ * @author JanRain, Inc.
+ */
+public class DiscoveryManager implements Serializable
+{
+    private static final long serialVersionUID = 2406514388629284823L;
+
+    private static final String cls = "com.janrain.openid.consumer.DiscoveryManager";
+    private static final Logger logger = Logger.getLogger(cls);
+
+    private List services = new ArrayList();
+    private int index;
+
+    private static class OpenIDServiceParser implements ServiceParser
+    {
+        private String yadisUrl;
+
+        public OpenIDServiceParser(String yadisUrl)
+        {
+            this.yadisUrl = yadisUrl;
+        }
+
+        public Set getSupportedTypes()
+        {
+            return OpenIDService.getOpenidTypeUris();
+        }
+
+        private static String getContent(Node n)
+        {
+            NodeList text = n.getChildNodes();
+            for (int i = 0; i < text.getLength(); i++)
+            {
+                Node t = text.item(i);
+                if (t.getNodeType() == Node.TEXT_NODE)
+                {
+                    return t.getNodeValue().trim();
+                }
+            }
+
+            return "";
+        }
+
+        public List parseService(Node n)
+        {
+            List result = new ArrayList();
+
+            List types = new ArrayList();
+            String delegate = yadisUrl;
+
+            NodeList nl = n.getChildNodes();
+
+            for (int i = 0; i < nl.getLength(); i++)
+            {
+                Node child = nl.item(i);
+                if (child.getNodeType() == Node.ELEMENT_NODE)
+                {
+                    String ns = child.getNamespaceURI();
+                    String name = child.getLocalName();
+
+                    if (XRDS.XRDNS.equals(ns) && name.equals("Type"))
+                    {
+                        types.add(getContent(child));
+                    }
+                    else if (OpenIDService.OPENID_1_0_NS.equals(ns)
+                            && name.equals("Delegate"))
+                    {
+                        delegate = getContent(child);
+                    }
+                }
+            }
+
+            for (int i = 0; i < nl.getLength(); i++)
+            {
+                Node child = nl.item(i);
+                if (child.getNodeType() == Node.ELEMENT_NODE
+                        && XRDS.XRDNS.equals(child.getNamespaceURI())
+                        && child.getLocalName().equals("URI"))
+                {
+                    int p = Integer.MAX_VALUE;
+                    String uri = getContent(child);
+                    NamedNodeMap attrs = child.getAttributes();
+
+                    Node priority = attrs.getNamedItem("priority");
+                    if (priority != null)
+                    {
+                        try
+                        {
+                            p = Integer.parseInt(priority.getNodeValue());
+                        }
+                        catch (NumberFormatException e)
+                        {
+                            // ignore, nothing to do
+                        }
+                    }
+
+                    result.add(new OpenIDService(yadisUrl, delegate, uri, true,
+                            types, p));
+                }
+            }
+
+            Collections.sort(result);
+            return result;
+        }
+
+    }
+
+    public DiscoveryManager(String url)
+    {
+        DiscoveryResult response;
+        try
+        {
+            response = DiscoveryResult.fromURI(url);
+        }
+        catch (DiscoveryFailure df)
+        {
+            // no fallback case here.
+            logger.warning("Unable to get a DiscoveryResult for " + url);
+            return;
+        }
+
+        String identityUrl = response.getNormalizedUri();
+
+        try
+        {
+            services = new XRDS(response).getServices(new OpenIDServiceParser(
+                    identityUrl));
+        }
+        catch (XRDSError e)
+        {
+            // do nothing
+        }
+
+        if (services.size() == 0)
+        {
+            String body;
+            if (response.isXRDS())
+            {
+                FetchResponse fr = HTTPFetcher.getFetcher().fetch(url);
+                identityUrl = fr.getFinalUrl();
+                body = fr.getContent();
+            }
+            else
+            {
+                body = response.getDecodedContent();
+            }
+
+            OpenIDService s = OpenIDService.fromHtml(identityUrl, body);
+            if (s.getServerUrl() != null)
+            {
+                services.add(s);
+            }
+        }
+    }
+
+    public boolean isEmpty()
+    {
+        return services.size() == 0;
+    }
+
+    public int size()
+    {
+        return services.size();
+    }
+
+    public boolean hasNext()
+    {
+        return index < services.size();
+    }
+
+    public OpenIDService getNext()
+    {
+        return (OpenIDService)services.get(index++);
+    }
+}

Added: incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/ErrorResponse.java
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/ErrorResponse.java?view=auto&rev=463013
==============================================================================
--- incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/ErrorResponse.java (added)
+++ incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/ErrorResponse.java Wed Oct 11 15:33:04 2006
@@ -0,0 +1,25 @@
+package com.janrain.openid.consumer;
+
+/**
+ * A response with a status of <code>StatusCode.ERROR</code>. This
+ * response indicates the identity server sent an error message.
+ * 
+ * @author JanRain, Inc.
+ */public class ErrorResponse extends Response
+{
+    private String message;
+
+    public ErrorResponse(String identityUrl, String message)
+    {
+        super(StatusCode.ERROR, identityUrl);
+        this.message = message;
+    }
+
+    /**
+     * @return the error message sent by the server
+     */
+    public String getMessage()
+    {
+        return message;
+    }
+}

Added: incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/FailureResponse.java
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/FailureResponse.java?view=auto&rev=463013
==============================================================================
--- incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/FailureResponse.java (added)
+++ incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/FailureResponse.java Wed Oct 11 15:33:04 2006
@@ -0,0 +1,25 @@
+package com.janrain.openid.consumer;
+
+/**
+ * A response with a status of <code>StatusCode.FAILURE</code>. This
+ * response indicates that the user's login attempt wasn't valid.
+ * 
+ * @author JanRain, Inc.
+ */public class FailureResponse extends Response
+{
+    private String message;
+
+    public FailureResponse(String identityUrl, String message)
+    {
+        super(StatusCode.FAILURE, identityUrl);
+        this.message = message;
+    }
+
+    /**
+     * @return a short description of why the login attempt was invalid
+     */
+    public String getMessage()
+    {
+        return message;
+    }
+}

Added: incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/GenericConsumer.java
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/GenericConsumer.java?view=auto&rev=463013
==============================================================================
--- incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/GenericConsumer.java (added)
+++ incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/GenericConsumer.java Wed Oct 11 15:33:04 2006
@@ -0,0 +1,395 @@
+package com.janrain.openid.consumer;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.janrain.openid.Association;
+import com.janrain.openid.Util;
+import com.janrain.openid.store.OpenIDStore;
+import com.janrain.url.FetchResponse;
+import com.janrain.url.HTTPFetcher;
+
+/**
+ * This class provides low-level OpenID logic to the Consumer class
+ * 
+ * @author JanRain, Inc.
+ */
+class GenericConsumer
+{
+    private static final String cls = "com.janrain.openid.consumer.OpenIDConsumer";
+    private static Logger logger = Logger.getLogger(cls);
+
+    private static long TOKEN_LIFETIME = 60 * 5; // five minutes in seconds
+    private static int NONCE_LEN = 8;
+    private static char [] NONCE_CHARS = ("abcdefghijklmnopqrstuvwzyzABCDEFGH"
+            + "IJKLMNOPQRSTUVWXYZ0123456789").toCharArray();
+
+    private static String NONCE_NAME = "nonce";
+
+    private OpenIDStore store;
+
+    public GenericConsumer(OpenIDStore store)
+    {
+        this.store = store;
+    }
+
+    public AuthRequest begin(OpenIDService s)
+    {
+        String nonce = createNonce();
+        Token token = new Token(store.getAuthKey(), s.getIdentityUrl(), s
+                .getDelegate(), s.getServerUrl());
+
+        Association assoc = getAssociation(s.getServerUrl());
+        AuthRequest request = new AuthRequest(token, assoc, s);
+        request.addReturnToArg(NONCE_NAME, nonce);
+        return request;
+    }
+
+    public Response complete(Map query, Token token)
+    {
+        String mode = ((String)query.get("openid.mode"));
+
+        if (token == null)
+        {
+            return new FailureResponse(null, "No session state found.");
+        }
+
+        if (mode.equals("cancel"))
+        {
+            return new CancelResponse(token.getConsumerId());
+        }
+        else if (mode.equals("error"))
+        {
+            String error = ((String)query.get("openid.error"));
+            return new ErrorResponse(token.getConsumerId(), error);
+        }
+        else if (mode.equals("id_res"))
+        {
+            Response r = doIdRes(query, token);
+            if (r == null)
+            {
+                String message = "Failure connecting to server to verify login";
+                return new FailureResponse(token.getConsumerId(), message);
+            }
+
+            if (r.getStatus() == StatusCode.SUCCESS)
+            {
+                return checkNonce(r, ((String)query.get(NONCE_NAME)));
+            }
+            else
+            {
+                return r;
+            }
+        }
+        else
+        {
+            return new FailureResponse(token.getConsumerId(),
+                    "Invalid openid.mode: " + mode);
+        }
+    }
+
+    private static String urlRegex = ".*?://(.*?)(/.*?)?(\\?.*?)?(#.*?)?";
+    private static Pattern urlPattern = Pattern.compile(urlRegex);
+
+    private Response checkNonce(Response resp, String nonce)
+    {
+        SuccessResponse sr = (SuccessResponse)resp;
+        String returnTo = sr.getReturnTo();
+
+        Matcher m = urlPattern.matcher(returnTo);
+        if (!m.matches())
+        {
+            String message = "Unable to parse the return_to url: " + returnTo;
+            return new FailureResponse(sr.getIdentityUrl(), message);
+        }
+
+        String query = m.group(3);
+        if (query == null)
+        {
+            String message = "The return_to url is missing a query section: "
+                    + returnTo;
+            return new FailureResponse(sr.getIdentityUrl(), message);
+        }
+
+        String [] args = query.split("[\\?\\&]");
+        for (int i = 0; i < args.length; i++)
+        {
+            String [] kv = args[i].split("=", 2);
+            if (kv.length != 2) continue;
+
+            if (kv[0].equals(NONCE_NAME))
+            {
+                if (kv[1].equals(nonce))
+                {
+                    if (store.useNonce(nonce))
+                    {
+                        return resp;
+                    }
+                    else
+                    {
+                        String message = "unknown nonce: " + nonce;
+                        return new FailureResponse(sr.getIdentityUrl(), message);
+                    }
+                }
+                else
+                {
+                    break;
+                }
+            }
+        }
+
+        String message = "The return_to url's nonce does not equal the signed one: "
+                + returnTo + " " + nonce;
+        return new FailureResponse(sr.getIdentityUrl(), message);
+    }
+
+    private String createNonce()
+    {
+        String nonce = Util.randomString(NONCE_LEN, NONCE_CHARS);
+        store.storeNonce(nonce);
+        return nonce;
+    }
+
+    private Map makeKVPost(Map args, String serverUrl)
+    {
+        String mode = (String)args.get("openid.mode");
+        String body = Util.encodeArgs(args);
+
+        FetchResponse fr = HTTPFetcher.getFetcher().fetch(serverUrl, body);
+
+        if (fr == null
+                || (fr.getStatusCode() != 200 && fr.getStatusCode() != 400))
+        {
+            String error = "openid.mode=" + mode + "bad response from server "
+                    + serverUrl + ": " + fr;
+            logger.log(Level.INFO, error);
+            return null;
+        }
+
+        Map result = Util.parseKVForm(fr.getContent());
+        if (fr.getStatusCode() == 400)
+        {
+            String msg = (String)result.get("error");
+            if (msg == null)
+            {
+                msg = "<no message from server>";
+            }
+            String error = "openid.mode=" + mode + ", error from server"
+                    + serverUrl + ": " + msg;
+            logger.log(Level.INFO, error);
+            return null;
+        }
+
+        return result;
+    }
+
+    private Response doIdRes(Map query, Token token)
+    {
+        String userSetupUrl = (String)query.get("openid.user_setup_url");
+        if (userSetupUrl != null)
+        {
+            return new SetupNeededResponse(token.getConsumerId(), userSetupUrl);
+        }
+
+        String returnTo = (String)query.get("openid.return_to");
+        String serverId = (String)query.get("openid.identity");
+        String assocHandle = (String)query.get("openid.assoc_handle");
+
+        if (returnTo == null || serverId == null || assocHandle == null)
+        {
+            return new FailureResponse(token.getConsumerId(),
+                    "Missing required field");
+        }
+
+        if (!serverId.equals(token.getServerId()))
+        {
+            return new FailureResponse(token.getConsumerId(),
+                    "server id (delegate) mismatch");
+        }
+
+        String signed = (String)query.get("openid.signed");
+
+        Association assoc = store.getAssociation(token.getServerUrl(),
+                assocHandle);
+        if (assoc == null)
+        {
+            // it's not an association we know about. fall back on dumb mode
+            if (checkAuth(query, token.getServerUrl()))
+            {
+                return SuccessResponse.fromQuery(token.getConsumerId(), query,
+                        signed);
+            }
+            else
+            {
+                return new FailureResponse(token.getConsumerId(),
+                        "Server denied check_authentication");
+            }
+        }
+
+        if (assoc.getRemainingLife() <= 0)
+        {
+            String msg = "Association with " + token.getServerUrl()
+                    + " expired";
+            return new FailureResponse(token.getConsumerId(), msg);
+        }
+
+        String sig = (String)query.get("openid.sig");
+        if (sig == null || signed == null)
+        {
+            return new FailureResponse(token.getConsumerId(),
+                    "Missing argument signature");
+        }
+
+        List signedList = Arrays.asList(signed.split(","));
+        String vSig = assoc.sign(signedList, query);
+
+        if (!vSig.equals(sig))
+        {
+            return new FailureResponse(token.getConsumerId(), "Bad Signature");
+        }
+
+        return SuccessResponse.fromQuery(token.getConsumerId(), query, signed);
+    }
+
+    private boolean checkAuth(Map query, String serverUrl)
+    {
+        Map request = createCheckAuthRequest(query);
+        if (request == null)
+        {
+            return false;
+        }
+
+        Map response = makeKVPost(request, serverUrl);
+        if (response == null)
+        {
+            return false;
+        }
+
+        return processCheckAuthResponse(response, serverUrl);
+    }
+
+    private Map createCheckAuthRequest(Map query)
+    {
+        String signed = (String)query.get("openid.signed");
+        if (signed == null)
+        {
+            logger.log(Level.INFO, "no signature found, aborting checkAuth");
+            return null;
+        }
+
+        String [] whitelist = {"assoc_handle", "sig", "signed",
+                "invalidate_handle"};
+
+        Set signedSet = new HashSet(Arrays.asList(whitelist));
+        signedSet.addAll(Arrays.asList(signed.split(",")));
+
+        Map result = new HashMap();
+        Iterator it = query.entrySet().iterator();
+        while (it.hasNext())
+        {
+            Map.Entry e = (Map.Entry)it.next();
+            String k = (String)e.getKey();
+            if (k.startsWith("openid.") && signedSet.contains(k.substring(7)))
+            {
+                result.put(k, e.getValue());
+            }
+        }
+
+        result.put("openid.mode", "check_authentication");
+        return result;
+    }
+
+    private boolean processCheckAuthResponse(Map response, String serverUrl)
+    {
+        String invalidateHandle = (String)response.get("invalidate_handle");
+        if (invalidateHandle != null)
+        {
+            store.removeAssociation(serverUrl, invalidateHandle);
+        }
+
+        String isValid = (String)response.get("is_valid");
+        if (isValid != null && isValid.equals("true"))
+        {
+            return true;
+        }
+        else
+        {
+            logger
+                    .log(Level.INFO,
+                            "Server responds that checkAuthentication call is not valid");
+            return false;
+        }
+    }
+
+    private Association getAssociation(String serverUrl)
+    {
+        if (store.isDumb())
+        {
+            return null;
+        }
+
+        Association assoc = store.getAssociation(serverUrl);
+
+        if (assoc == null || assoc.getRemainingLife() < TOKEN_LIFETIME)
+        {
+            String sessionType = serverUrl.startsWith("https") ? "" : "DH-SHA1";
+            Associator a = new Associator("HMAC-SHA1", sessionType);
+            assoc = a.createAssociation(serverUrl);
+            if (assoc != null)
+            {
+                store.storeAssociation(serverUrl, assoc);
+            }
+        }
+
+        return assoc;
+    }
+
+    public static char [] getNonceChars()
+    {
+        return NONCE_CHARS;
+    }
+
+    public static void setNonceChars(char [] nonceChars)
+    {
+        NONCE_CHARS = nonceChars;
+    }
+
+    public static int getNonceLen()
+    {
+        return NONCE_LEN;
+    }
+
+    public static void setNonceLen(int nonceLen)
+    {
+        NONCE_LEN = nonceLen;
+    }
+
+    public static long getTokenLifetime()
+    {
+        return TOKEN_LIFETIME;
+    }
+
+    public static void setTokenLifetime(long tokenLifetime)
+    {
+        TOKEN_LIFETIME = tokenLifetime;
+    }
+
+    public static String getNonceName()
+    {
+        return NONCE_NAME;
+    }
+
+    public static void setNonceName(String nonceName)
+    {
+        GenericConsumer.NONCE_NAME = nonceName;
+    }
+}

Added: incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/LinkParser.java
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/LinkParser.java?view=auto&rev=463013
==============================================================================
--- incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/LinkParser.java (added)
+++ incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/LinkParser.java Wed Oct 11 15:33:04 2006
@@ -0,0 +1,201 @@
+/*
+ * LinkParser.java Created on December 20, 2005, 4:57 PM
+ */
+
+package com.janrain.openid.consumer;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * This class handles finding link tags if the library falls back on old-style
+ * discovery.
+ * 
+ * @author JanRain, Inc.
+ */
+public class LinkParser
+{
+    private static final String cls = "com.janrain.openid.consumer.LinkParser";
+    private static Logger logger = Logger.getLogger(cls);
+
+    private static int flags = Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE
+            | Pattern.DOTALL | Pattern.COMMENTS;
+
+    private static Pattern removed = fromResource("removed.re");
+    private static Pattern html = fromResource("html.re");
+    private static Pattern head = fromResource("head.re");
+    private static Pattern link = fromResource("link.re");
+    private static Pattern attr = fromResource("attr.re");
+
+    private static Pattern fromResource(String name)
+    {
+        logger.entering(cls, "fromResource", name);
+        try
+        {
+            InputStream is = LinkParser.class.getResourceAsStream(name);
+            if (is == null)
+            {
+                logger.log(Level.SEVERE, "Unable to locate resource " + name);
+                logger.exiting(cls, "fromResource", null);
+                return null;
+            }
+            Reader r = new InputStreamReader(is, "UTF-8");
+
+            StringBuffer pat = new StringBuffer();
+
+            char [] buffer = new char[1024];
+
+            int c;
+            while ((c = r.read(buffer, 0, buffer.length)) >= 0)
+            {
+                pat.append(buffer, 0, c);
+            }
+
+            Pattern result = Pattern.compile(pat.toString(), flags);
+            logger.exiting(cls, "fromResource", result);
+            return result;
+        }
+        catch (IOException e)
+        {
+            logger.log(Level.SEVERE, "Exception reading resource " + name, e);
+            logger.exiting(cls, "fromResource", null);
+            return null;
+        }
+    }
+
+    private static Pattern ampRe = Pattern.compile("&amp;", flags);
+    private static Pattern ltRe = Pattern.compile("&lt;", flags);
+    private static Pattern gtRe = Pattern.compile("&gt;", flags);
+    private static Pattern quotRe = Pattern.compile("&quot;", flags);
+
+    private static String replaceEntities(String s)
+    {
+        String result = ltRe.matcher(s).replaceAll("<");
+        result = gtRe.matcher(result).replaceAll(">");
+        result = quotRe.matcher(result).replaceAll("\"");
+        result = ampRe.matcher(result).replaceAll("&");
+
+        return result;
+    }
+
+    public static List parseLinkAttrs(String input)
+    {
+        logger.entering(cls, "parseLinkAttrs", input);
+        List result = new ArrayList();
+        String stripped = removed.matcher(input).replaceAll("");
+
+        Matcher htmlMatcher = html.matcher(stripped);
+        if (!htmlMatcher.find() || htmlMatcher.group(2) == null)
+        {
+            logger.exiting(cls, "parseLinkAttrs", result);
+            return result;
+        }
+
+        Matcher headMatcher = head.matcher(htmlMatcher.group(2));
+        if (!headMatcher.find() || headMatcher.group(2) == null)
+        {
+            logger.exiting(cls, "parseLinkAttrs", result);
+            return result;
+        }
+
+        String head = headMatcher.group(2);
+        Matcher linkMatcher = link.matcher(head);
+
+        while (linkMatcher.find())
+        {
+            Map m = new HashMap();
+
+            Matcher attrMatcher = attr.matcher(head.substring(linkMatcher
+                    .start() + 5));
+            while (attrMatcher.find())
+            {
+                if (attrMatcher.group(5) != null)
+                {
+                    break;
+                }
+
+                String attrName = attrMatcher.group(1);
+                String attrVal = attrMatcher.group(3) != null ? attrMatcher
+                        .group(3) : attrMatcher.group(4);
+
+                m.put(attrName.toLowerCase(), replaceEntities(attrVal));
+            }
+            result.add(new LinkTag(m));
+        }
+
+        logger.exiting(cls, "parseLinkAttrs", result);
+        return result;
+    }
+
+    public static String findFirstHref(List linkList, String targetRel)
+    {
+        logger.entering(cls, "findFirstHref",
+                new Object[] {linkList, targetRel});
+
+        targetRel = targetRel.toLowerCase();
+
+        Iterator it = linkList.iterator();
+        while (it.hasNext())
+        {
+            LinkTag l = (LinkTag)it.next();
+
+            String rel = l.getAttribute("rel");
+            if (rel == null)
+            {
+                continue;
+            }
+
+            String [] pieces = rel.split("\\s+");
+            for (int i = 0; i < pieces.length; i++)
+            {
+                if (pieces[i].toLowerCase().equals(targetRel))
+                {
+                    String result = l.getAttribute("href");
+                    logger.exiting(cls, "findFirstHref", result);
+                    return result;
+                }
+            }
+        }
+
+        logger.exiting(cls, "findFirstHref", null);
+        return null;
+    }
+
+    public static class LinkTag
+    {
+        private Map attrs;
+
+        private LinkTag(Map attrs)
+        {
+            // intentionally not defensively copying
+            this.attrs = attrs;
+        }
+
+        public int size()
+        {
+            return attrs.size();
+        }
+
+        public String getAttribute(String name)
+        {
+            return (String)attrs.get(name);
+        }
+
+        public Set getAttributeNames()
+        {
+            return attrs.keySet();
+        }
+    }
+}

Added: incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/OpenIDService.java
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/OpenIDService.java?view=auto&rev=463013
==============================================================================
--- incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/OpenIDService.java (added)
+++ incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/OpenIDService.java Wed Oct 11 15:33:04 2006
@@ -0,0 +1,165 @@
+package com.janrain.openid.consumer;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import com.janrain.yadis.Service;
+
+/**
+ * This class provides a container for information about the identity and server
+ * being verified.
+ * 
+ * @author Janrain, Inc.
+ */
+public class OpenIDService implements Service, Serializable
+{
+    private static final long serialVersionUID = -8447354556218049438L;
+    public static final String OPENID_1_0_NS = "http://openid.net/xmlns/1.0";
+    public static final String OPENID_1_2_TYPE = "http://openid.net/signon/1.2";
+    public static final String OPENID_1_1_TYPE = "http://openid.net/signon/1.1";
+    public static final String OPENID_1_0_TYPE = "http://openid.net/signon/1.0";
+
+    private static Set openidTypeUris = new HashSet();
+    static
+    {
+        openidTypeUris.add(OPENID_1_0_TYPE);
+        openidTypeUris.add(OPENID_1_1_TYPE);
+        openidTypeUris.add(OPENID_1_2_TYPE);
+    }
+
+    private String identityUrl;
+    private String serverUrl;
+    private String delegate;
+
+    private boolean fromYadis;
+    private List typeUris;
+    private int priority = Integer.MAX_VALUE;
+
+    protected OpenIDService()
+    {
+        // empty for static factory methods
+    }
+
+    public OpenIDService(String identity, String delegate, String server,
+                         boolean fromYadis, List typeUris, int priority)
+    {
+        identityUrl = identity;
+        serverUrl = server;
+        this.delegate = delegate;
+        this.fromYadis = fromYadis;
+        this.typeUris = typeUris;
+        this.priority = priority;
+    }
+
+    public static OpenIDService fromHtml(String uri, String html)
+    {
+        OpenIDService result = new OpenIDService();
+
+        result.identityUrl = uri;
+
+        List linkAttrs = LinkParser.parseLinkAttrs(html);
+        result.serverUrl = LinkParser.findFirstHref(linkAttrs, "openid.server");
+        result.delegate = LinkParser
+                .findFirstHref(linkAttrs, "openid.delegate");
+
+        result.typeUris = new ArrayList();
+        result.typeUris.add(OPENID_1_0_TYPE);
+        return result;
+    }
+
+    /**
+     * @return the identifier the server will be asked to confirm. This will not
+     *         necessarily match the identifier the user is proving they own, so
+     *         should not be used as their identifier
+     */
+    public String getDelegate()
+    {
+        return delegate == null ? identityUrl : delegate;
+    }
+
+    /**
+     * @return the identifier the user is proving they own
+     */
+    public String getIdentityUrl()
+    {
+        return identityUrl;
+    }
+
+    /**
+     * @return the Url of the identity server being asked about the identity
+     */
+    public String getServerUrl()
+    {
+        return serverUrl;
+    }
+
+    /**
+     * @return a list of strings indicating what forms of OpenID the server
+     *         provides, as well as extensions
+     */
+    public List getTypeUris()
+    {
+        return typeUris;
+    }
+
+    /**
+     * @return true if this came from a Yadis discovery, false if it came from
+     *         old-style OpenID discovery
+     */
+    public boolean isFromYadis()
+    {
+        return fromYadis;
+    }
+
+    protected void setDelegate(String delegate)
+    {
+        this.delegate = delegate;
+    }
+
+    protected void setFromYadis(boolean fromYadis)
+    {
+        this.fromYadis = fromYadis;
+    }
+
+    protected void setIdentityUrl(String identityUrl)
+    {
+        this.identityUrl = identityUrl;
+    }
+
+    protected void setServerUrl(String serverUrl)
+    {
+        this.serverUrl = serverUrl;
+    }
+
+    protected void setTypeUris(List typeUris)
+    {
+        this.typeUris = typeUris;
+    }
+
+    public static Set getOpenidTypeUris()
+    {
+        return Collections.unmodifiableSet(openidTypeUris);
+    }
+
+    public int compareTo(Object obj)
+    {
+        OpenIDService o = (OpenIDService)obj;
+        if (priority > o.priority)
+        {
+            return 1;
+        }
+        else if (priority < o.priority)
+        {
+            return -1;
+        }
+        else
+        {
+            return 0;
+        }
+    }
+
+}

Added: incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/Response.java
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/Response.java?view=auto&rev=463013
==============================================================================
--- incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/Response.java (added)
+++ incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/Response.java Wed Oct 11 15:33:04 2006
@@ -0,0 +1,38 @@
+package com.janrain.openid.consumer;
+
+/**
+ * This is the superclass for the various responses the OpenID library can
+ * result in. Subclasses contain more information about what went on in their
+ * specific case.
+ * 
+ * @author JanRain, Inc.
+ */
+public abstract class Response
+{
+    private StatusCode status;
+    private String identityUrl;
+
+    protected Response(StatusCode status, String identityUrl)
+    {
+        this.status = status;
+        this.identityUrl = identityUrl;
+    }
+
+    /**
+     * @return the status of this Response, for easier branching based on status
+     *         than checking for a particular subclass being returned
+     */
+    public StatusCode getStatus()
+    {
+        return status;
+    }
+
+    /**
+     * @return the identity url the user was asserting, or <code>null</code>
+     *         if unknown.
+     */
+    public String getIdentityUrl()
+    {
+        return identityUrl;
+    }
+}

Added: incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/SetupNeededResponse.java
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/SetupNeededResponse.java?view=auto&rev=463013
==============================================================================
--- incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/SetupNeededResponse.java (added)
+++ incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/SetupNeededResponse.java Wed Oct 11 15:33:04 2006
@@ -0,0 +1,29 @@
+package com.janrain.openid.consumer;
+
+/**
+ * A response with a status of <code>StatusCode.SETUP_NEEDED</code>. This
+ * response indicates that the server didn't have enough information to
+ * determine the user's identity. This should only be returned if immediate mode
+ * was used. If it is, the user should be redirected to the setup Url to finish
+ * the login procedure.
+ * 
+ * @author JanRain, Inc.
+ */
+public class SetupNeededResponse extends Response
+{
+    private String setupUrl;
+
+    public SetupNeededResponse(String identityUrl, String setupUrl)
+    {
+        super(StatusCode.SETUP_NEEDED, identityUrl);
+        this.setupUrl = setupUrl;
+    }
+
+    /**
+     * @return the setup Url that the server provided.
+     */
+    public String getSetupUrl()
+    {
+        return setupUrl;
+    }
+}

Added: incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/StatusCode.java
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/StatusCode.java?view=auto&rev=463013
==============================================================================
--- incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/StatusCode.java (added)
+++ incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/StatusCode.java Wed Oct 11 15:33:04 2006
@@ -0,0 +1,69 @@
+/*
+ * StatusCode.java Created on January 25, 2006, 4:27 PM
+ */
+
+package com.janrain.openid.consumer;
+
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+
+/**
+ * This class is a typesafe enumeration construct for enumerating the possible
+ * final conditions of an OpenID request.
+ * 
+ * @author JanRain, Inc.
+ */
+public class StatusCode implements Serializable
+{
+    private static final long serialVersionUID = 2289980756918482315L;
+
+    private static int nextOrdinal = 0;
+
+    /**
+     * This code indicates a successful authentication request
+     */
+    public static final StatusCode SUCCESS = new StatusCode("success");
+
+    /**
+     * This code indicates a failed authentication request
+     */
+    public static final StatusCode FAILURE = new StatusCode("failure");
+
+    /**
+     * This code indicates the server reported an error
+     */
+    public static final StatusCode ERROR = new StatusCode("error");
+
+    /**
+     * This code indicates that the user needs to do additional work to prove
+     * their identity
+     */
+    public static final StatusCode SETUP_NEEDED = new StatusCode("setup needed");
+
+    /**
+     * This code indicates that the user cancelled their login request
+     */
+    public static final StatusCode CANCELLED = new StatusCode("cancelled");
+
+    private static final StatusCode [] PRIVATE_VALUES = {SUCCESS, FAILURE,
+            ERROR, SETUP_NEEDED, CANCELLED};
+
+    private String name;
+
+    private final int ordinal = nextOrdinal++;
+
+    private StatusCode(String name)
+    {
+        this.name = name;
+    }
+
+    public String toString()
+    {
+        return name;
+    }
+
+    private Object readResolve() throws ObjectStreamException
+    {
+        return PRIVATE_VALUES[ordinal];
+    }
+}

Added: incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/SuccessResponse.java
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/SuccessResponse.java?view=auto&rev=463013
==============================================================================
--- incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/SuccessResponse.java (added)
+++ incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/SuccessResponse.java Wed Oct 11 15:33:04 2006
@@ -0,0 +1,93 @@
+package com.janrain.openid.consumer;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * A response with a status of <code>StatusCode.Success</code>. This response
+ * indicates that the user's authentication request was successful. It's
+ * important to use the value returned by <code>getIdentityUrl</code> as the
+ * user's proven identifier.
+ * 
+ * @author JanRain, Inc.
+ */
+public class SuccessResponse extends Response
+{
+    private Map signedArgs;
+
+    public static SuccessResponse fromQuery(String identityUrl, Map query,
+            String signed)
+    {
+        String [] signedNames = signed.split(",");
+        Map signedArgs = new HashMap();
+        for (int i = 0; i < signedNames.length; i++)
+        {
+            String key = "openid." + signedNames[i];
+            Object value = query.get(key);
+            if (value == null)
+            {
+                value = "";
+            }
+            signedArgs.put(key, value);
+        }
+
+        return new SuccessResponse(identityUrl, signedArgs);
+    }
+
+    public SuccessResponse(String identityUrl, Map signedArgs)
+    {
+        super(StatusCode.SUCCESS, identityUrl);
+        this.signedArgs = signedArgs;
+    }
+
+    /**
+     * @return the entire set of arguments signed by the server
+     */
+    public Map getSignedArgs()
+    {
+        return Collections.unmodifiableMap(signedArgs);
+    }
+
+    /**
+     * This method returns a Map of signed extension arguments returned by the
+     * server, based on an extension prefix.
+     * 
+     * @param prefix
+     *            the extension prefix to check for
+     * @return the signed arguments in that extension namespace
+     */
+    public Map getExtensionResponse(String prefix)
+    {
+        Map result = new HashMap();
+
+        prefix = "openid." + prefix + ".";
+        int len = prefix.length();
+
+        Iterator it = signedArgs.entrySet().iterator();
+        while (it.hasNext())
+        {
+            Map.Entry e = (Map.Entry)it.next();
+            String k = (String)e.getKey();
+            if (k.startsWith(prefix))
+            {
+                String rk = k.substring(len);
+                String v = (String)e.getValue();
+                result.put(rk, v);
+            }
+        }
+
+        return Collections.unmodifiableMap(result);
+    }
+
+    /**
+     * @return the signed <code>openid.return_to</code> argument from this
+     *         response, or <code>null</code> if there wasn't a signed
+     *         <code>openid.return_to</code> field
+     */
+    public String getReturnTo()
+    {
+        return (String)signedArgs.get("openid.return_to");
+    }
+}

Added: incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/Token.java
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/Token.java?view=auto&rev=463013
==============================================================================
--- incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/Token.java (added)
+++ incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/Token.java Wed Oct 11 15:33:04 2006
@@ -0,0 +1,196 @@
+/*
+ * Token.java Created on January 26, 2006, 10:36 AM
+ */
+
+package com.janrain.openid.consumer;
+
+import java.io.Serializable;
+import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.janrain.openid.Util;
+
+/**
+ * This class contains information the application needs to preserve between the
+ * first and second requests of the OpenID login.  
+ * 
+ * @author JanRain, Inc.
+ */
+public class Token implements Serializable
+{
+    static final long serialVersionUID = -9130413740366967702L;
+    private static final String cls = "com.janrain.openid.consumer.Token";
+    private static Logger logger = Logger.getLogger(cls);
+
+    private String consumerId;
+    private String serverId;
+    private String serverUrl;
+    private long timestamp;
+    private String serialized;
+    private byte [] joined;
+    private byte [] sig;
+
+    public Token(byte [] key, String consumerId, String serverId,
+                 String serverUrl)
+    {
+        this.consumerId = consumerId;
+        this.serverId = serverId;
+        this.serverUrl = serverUrl;
+
+        timestamp = Util.getTimeStamp();
+        join();
+
+        sig = Util.hmacSha1(key, joined);
+
+        byte [] full = new byte[joined.length + sig.length];
+        System.arraycopy(sig, 0, full, 0, sig.length);
+        System.arraycopy(joined, 0, full, sig.length, joined.length);
+
+        serialized = Util.toBase64(full);
+    }
+
+    private Token()
+    {
+        // for static method below
+    }
+
+    public static Token fromSerialized(String serialized)
+    {
+        String mth = "fromSerialized";
+        logger.entering(cls, mth, serialized);
+        Token result = new Token();
+
+        byte [] full = Util.fromBase64(serialized);
+
+        if (full.length <= 20)
+        {
+            logger.logp(Level.INFO, cls, mth, "Invalid serialized form");
+            logger.exiting(cls, mth, result);
+            return result;
+        }
+
+        byte [] sig = new byte[20];
+        byte [] joined = new byte[full.length - 20];
+        System.arraycopy(full, 0, sig, 0, sig.length);
+        System.arraycopy(full, sig.length, joined, 0, joined.length);
+
+        result.serialized = serialized;
+        result.sig = sig;
+
+        String [] s = new String[4];
+        int start = 0;
+        int end = 0;
+
+        try
+        {
+            for (int i = 0; i < s.length; i++)
+            {
+                while (end < joined.length && joined[end] != 0)
+                    end++;
+                s[i] = new String(joined, start, end - start, "UTF-8");
+
+                if (end < joined.length)
+                {
+                    start = ++end; // skip the delimiter byte
+                }
+                else
+                {
+                    start = end;
+                }
+            }
+        }
+        catch (UnsupportedEncodingException e)
+        {
+            logger.log(Level.SEVERE, "No UTF-8?", e); // huh?
+        }
+
+        try
+        {
+            result.timestamp = Long.parseLong(s[0]);
+        }
+        catch (NumberFormatException e)
+        {
+            logger.logp(Level.INFO, cls, mth, "Unable to parse timestamp", e);
+        }
+
+        result.consumerId = s[1];
+        result.serverId = s[2];
+        result.serverUrl = s[3];
+
+        result.join(); // recalculate joined, in case something didn't parse
+
+        logger.exiting(cls, mth, result);
+        return result;
+    }
+
+    private void join()
+    {
+        byte [][] working = new byte[4][];
+
+        try
+        {
+            working[0] = String.valueOf(timestamp).getBytes("UTF-8");
+            working[1] = String.valueOf(consumerId).getBytes("UTF-8");
+            working[2] = String.valueOf(serverId).getBytes("UTF-8");
+            working[3] = String.valueOf(serverUrl).getBytes("UTF-8");
+        }
+        catch (UnsupportedEncodingException e)
+        {
+            // This will still never happen
+            logger.log(Level.SEVERE, "No UTF-8?", e);
+            return;
+        }
+
+        int rlen = working.length - 1;
+        for (int i = 0; i < working.length; i++)
+            rlen += working[i].length;
+
+        joined = new byte[rlen];
+
+        int index = 0;
+
+        for (int i = 0; i < working.length; i++)
+        {
+            System.arraycopy(working[i], 0, joined, index, working[i].length);
+            index += working[i].length + 1; // adds the padding byte
+        }
+    }
+
+    public String getConsumerId()
+    {
+        return consumerId;
+    }
+
+    public String getServerId()
+    {
+        return serverId;
+    }
+
+    public String getServerUrl()
+    {
+        return serverUrl;
+    }
+
+    public String getSerialized()
+    {
+        return serialized;
+    }
+
+    public long getTimeStamp()
+    {
+        return timestamp;
+    }
+
+    public boolean verify(byte [] key)
+    {
+        byte [] calculated = Util.hmacSha1(key, joined);
+        return Arrays.equals(calculated, sig);
+    }
+
+    public boolean isStillValid(long lifetime)
+    {
+        return getTimeStamp() + lifetime > Util.getTimeStamp();
+    }
+}

Added: incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/attr.re
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/attr.re?view=auto&rev=463013
==============================================================================
--- incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/attr.re (added)
+++ incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/attr.re Wed Oct 11 15:33:04 2006
@@ -0,0 +1,17 @@
+# Must start with a sequence of word-characters, followed by an equals sign
+(\w+)=
+
+# Then either a quoted or unquoted attribute
+(?:
+
+ # Match everything that's between matching quote marks
+ (["'])(.*?)\2
+|
+
+ # If the value is not quoted, match up to whitespace
+ ((?:[^\s<>/]|/(?!>))+)
+)
+
+|
+
+([<>])

Added: incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/head.re
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/head.re?view=auto&rev=463013
==============================================================================
--- incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/head.re (added)
+++ incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/head.re Wed Oct 11 15:33:04 2006
@@ -0,0 +1,26 @@
+# Starts with the tag name at a word boundary, where the tag name is
+# not a namespace
+<head\b(?!:)
+
+# All of the stuff up to a ">", hopefully attributes.
+([^>]*?)
+
+(?: # Match a short tag
+    />
+
+|   # Match a full tag
+    >
+
+    # match the contents of the full tag
+    (.*?)
+
+    # Closed by
+    (?: # One of the specified close tags
+        </?(?:head|body)\s*>
+
+        # End of the string
+    |   \Z
+
+    )
+
+)

Added: incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/html.re
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/html.re?view=auto&rev=463013
==============================================================================
--- incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/html.re (added)
+++ incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/html.re Wed Oct 11 15:33:04 2006
@@ -0,0 +1,26 @@
+# Starts with the tag name at a word boundary, where the tag name is
+# not a namespace
+<html\b(?!:)
+
+# All of the stuff up to a ">", hopefully attributes.
+([^>]*?)
+
+(?: # Match a short tag
+    />
+
+|   # Match a full tag
+    >
+
+    # contents
+    (.*?)
+
+    # Closed by
+    (?: # One of the specified close tags
+        </?html\s*>
+
+        # End of the string
+    |   \Z
+
+    )
+
+)

Added: incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/link.re
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/link.re?view=auto&rev=463013
==============================================================================
--- incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/link.re (added)
+++ incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/link.re Wed Oct 11 15:33:04 2006
@@ -0,0 +1 @@
+<link\b(?!:)

Added: incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/removed.re
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/removed.re?view=auto&rev=463013
==============================================================================
--- incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/removed.re (added)
+++ incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/consumer/removed.re Wed Oct 11 15:33:04 2006
@@ -0,0 +1,13 @@
+  # Comments
+  <!--.*?-->
+
+  # CDATA blocks
+| <!\[CDATA\[.*?\]\]>
+
+  # script blocks
+| <script\b
+
+  # make sure script is not an XML namespace
+  (?!:)
+
+  [^>]*>.*?</script>

Added: incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/store/DumbStore.java
URL: http://svn.apache.org/viewvc/incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/store/DumbStore.java?view=auto&rev=463013
==============================================================================
--- incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/store/DumbStore.java (added)
+++ incubator/heraldry/libraries/java/trunk/src/com/janrain/openid/store/DumbStore.java Wed Oct 11 15:33:04 2006
@@ -0,0 +1,120 @@
+package com.janrain.openid.store;
+
+import java.io.UnsupportedEncodingException;
+
+import com.janrain.openid.Association;
+import com.janrain.openid.Util;
+
+/**
+ * <p>
+ * This is a store for use in the worst case, when you have no way of saving
+ * state on the consumer site. Using this store makes the consumer vulnerable to
+ * replay attacks (though only within the lifespan of the tokens), as it's
+ * unable to use nonces. Avoid using this store if it is at all possible.
+ * </p>
+ * <p>
+ * Most of the methods of this class are implementation details. Users of this
+ * class need to worry only about the constructor.
+ * </p>
+ * 
+ * @author JanRain, Inc
+ */
+public class DumbStore extends OpenIDStore
+{
+    private byte [] authKey;
+
+    /**
+     * <p>
+     * Creates a new <code>DumbStore</code> instance. For the security of the
+     * tokens generated by the library, this class attempts to at least have a
+     * secure implementation of <code>getAuthKey</code>.
+     * </p>
+     * <p>
+     * When you create an instance of this class, pass in a secret phrase. The
+     * phrase is hashed with sha1 to make it the correct length and form for an
+     * auth key. That allows you to use a long string as the secret phrase,
+     * which means you can make it very difficult to guess.
+     * </p>
+     * <p>
+     * Each <code>DumbStore</code> instance that is created for use by your
+     * consumer site needs to use the same <code>secret_phrase</code>.
+     * </p>
+     * 
+     * @param secretPhrase
+     *            the phrase used to create the auth key returned by
+     *            <code>getAuthKey<code>
+     */
+    public DumbStore(String secretPhrase)
+    {
+        try
+        {
+            authKey = Util.sha1(secretPhrase.getBytes("UTF-8"));
+        }
+        catch (UnsupportedEncodingException e)
+        {
+            // this will never happen
+        }
+    }
+
+    /**
+     * This implementation always returns <code>null</code>
+     * 
+     * @return <code>null</code>
+     */
+    public Association getAssociation(String serverUrl, String handle)
+    {
+        return null;
+    }
+
+    /**
+     * @return the byte array calculated in the constructor
+     */
+    public byte [] getAuthKey()
+    {
+        return authKey;
+    }
+
+    /**
+     * This implementation always returns <code>true</code>
+     * 
+     * @return <code>true</code>
+     */
+    public boolean isDumb()
+    {
+        return true;
+    }
+
+    /**
+     * This implementation does nothing
+     */
+    public boolean removeAssociation(String serverUrl, String handle)
+    {
+        return false;
+    }
+
+    /**
+     * This implementation does nothing
+     */
+    public void storeAssociation(String serverUrl, Association assoc)
+    {
+        // do nothing
+    }
+
+    public void storeNonce(String nonce)
+    {
+        // do nothing
+    }
+
+    /**
+     * In a system truly limited to dumb mode, nonces must all be accepted. This
+     * therefore always returns <code>true</code>, which makes replay attacks feasible
+     * during the lifespan of the token.
+     * 
+     * @return <code>true</code>
+     */
+    public boolean useNonce(String nonce)
+    {
+        return true;
+    }
+
+}



Mime
View raw message