Adding JMCR-Stable version
[Benchmarks_CSolver.git] / JMCR-Stable / real-world application / jigsaw / src / org / w3c / jigsaw / auth / DigestAuthFilter.java
diff --git a/JMCR-Stable/real-world application/jigsaw/src/org/w3c/jigsaw/auth/DigestAuthFilter.java b/JMCR-Stable/real-world application/jigsaw/src/org/w3c/jigsaw/auth/DigestAuthFilter.java
new file mode 100644 (file)
index 0000000..3cb3f7c
--- /dev/null
@@ -0,0 +1,557 @@
+// DigestAuthFilter.java\r
+// $Id: DigestAuthFilter.java,v 1.2 2010/06/15 17:53:03 smhuang Exp $\r
+// (c) COPYRIGHT MIT, INRIA and Keio, 1999.\r
+// Please first read the full copyright statement in file COPYRIGHT.html\r
+\r
+package org.w3c.jigsaw.auth;\r
+\r
+import java.util.Date;\r
+import java.security.MessageDigest;\r
+import java.security.NoSuchAlgorithmException;\r
+import org.w3c.tools.resources.Attribute;\r
+import org.w3c.tools.resources.AttributeRegistry;\r
+import org.w3c.tools.resources.FramedResource;\r
+import org.w3c.tools.resources.IntegerAttribute;\r
+import org.w3c.tools.resources.InvalidResourceException;\r
+import org.w3c.tools.resources.ProtocolException;\r
+import org.w3c.tools.resources.ReplyInterface;\r
+import org.w3c.tools.resources.RequestInterface;\r
+import org.w3c.tools.resources.ResourceReference;\r
+import org.w3c.tools.resources.StringArrayAttribute;\r
+import org.w3c.tools.resources.StringAttribute;\r
+import org.w3c.jigsaw.http.Client;\r
+import org.w3c.jigsaw.http.HTTPException;\r
+import org.w3c.jigsaw.http.Reply;\r
+import org.w3c.jigsaw.http.Request;\r
+import org.w3c.jigsaw.http.httpd;\r
+import org.w3c.jigsaw.html.HtmlGenerator;\r
+import org.w3c.www.http.HTTP;\r
+import org.w3c.www.http.HttpChallenge;\r
+import org.w3c.www.http.HttpCredential;\r
+import org.w3c.www.http.HttpFactory;\r
+import org.w3c.jigsaw.frames.HTTPFrame;\r
+\r
+import org.w3c.util.StringUtils;\r
+\r
+import org.w3c.tools.resources.ProtocolException;\r
+\r
+/**\r
+ * Internal exception class\r
+ */\r
+\r
+class DigestAuthFilterException extends Exception {\r
+\r
+    DigestAuthFilterException (String msg) {\r
+       super (msg);\r
+    }\r
+}\r
+\r
+/**\r
+ * DigestAuthFilter provides for both IP and digest authentication.\r
+ * This is really a first implementation. It looses on several points:\r
+ * <ul>\r
+ * <li>AuthUser instances, being a subclass of resource dump their classes\r
+ * along with their attributes, although here we know that they will all\r
+ * be instances of AuthUser.\r
+ * <li>The way the ipmatcher is maintained doesn't make much sense.\r
+ * <li>The way groups are handled is no good.\r
+ * <li>The SimpleResourceStore is not an adequat store for the user database,\r
+ * it should rather use the jdbmResourceStore (not written yet).\r
+ * </ul>\r
+ * However, this provides for the digest functionnalities.\r
+ */\r
+\r
+public class DigestAuthFilter extends AuthFilter {\r
+\r
+    public class DigestAuthContext {\r
+       String dac_user      = null;\r
+       String dac_realm     = null;\r
+       String dac_nonce     = null;\r
+       String dac_uri       = null;\r
+       String dac_response  = null;\r
+       String dac_algorithm = null;\r
+       String dac_method    = null;\r
+       boolean stale        = false;\r
+       // qop and nc may be added at some point\r
+       // maybe not as it implies session tracking\r
+\r
+       DigestAuthContext (Request request)\r
+           throws DigestAuthFilterException, ProtocolException\r
+       {\r
+           HttpCredential credential = null;\r
+           \r
+           credential = (request.isProxy()\r
+                         ? request.getProxyAuthorization()\r
+                         : request.getAuthorization());\r
+           if ( ! credential.getScheme().equalsIgnoreCase("Digest") ) {\r
+               String msg = ("Invalid authentication scheme \""\r
+                             + credential.getScheme()\r
+                             + " expecting \"Digest\"");\r
+               throw new DigestAuthFilterException (msg) ;\r
+           }\r
+           // now split things and decode things\r
+           dac_user     = credential.getAuthParameter("username");\r
+           dac_uri      = credential.getAuthParameter("uri");\r
+           dac_response = credential.getAuthParameter("response");\r
+           dac_realm    = credential.getAuthParameter("realm");\r
+           dac_method   = request.getMethod();\r
+           dac_nonce    = credential.getAuthParameter("nonce");\r
+           if (dac_user == null || dac_uri == null || dac_response == null ||\r
+               dac_realm == null) {\r
+               String msg = ("Invalid authentication header");\r
+               throw new DigestAuthFilterException(msg);\r
+           }\r
+       }\r
+       \r
+       boolean authenticate(String username, String realm, String passwd)\r
+       {\r
+           stale = false;\r
+           if (!dac_user.equals(username))\r
+               return false;\r
+           if (!dac_realm.equals(realm))\r
+               return false;\r
+           if (dac_algorithm != null && !dac_algorithm.equals(getAlgorithm()))\r
+               return false;\r
+           if (!dac_nonce.equals(nonce)) {\r
+               if (!dac_nonce.equals(old_nonce)) {\r
+                   // check if the user knows the right passwd\r
+                   String a1, a2, ha1, ha2;\r
+                   a1 = username + ":" + realm + ":" + passwd;\r
+                   a2 = dac_method + ":" + dac_uri;\r
+                   MessageDigest md = null;\r
+                   try {\r
+                       md = MessageDigest.getInstance(getAlgorithm());\r
+                   } catch (NoSuchAlgorithmException algex) {\r
+                       // fatal error, can't authenticate\r
+                       return false;\r
+                   }\r
+                   md.update(a1.getBytes());\r
+                   ha1 = StringUtils.toHexString(md.digest());\r
+                   md.reset();\r
+                   md.update(a2.getBytes());\r
+                   ha2 = StringUtils.toHexString(md.digest());\r
+                   md.reset();\r
+                   String kd, hkd;\r
+                   // KD( H(A1), unq(nonce-value) ":" H(A2)\r
+                   kd = ha1 + ":" + dac_nonce + ":" + ha2;\r
+                   md.update(kd.getBytes());\r
+                   hkd = StringUtils.toHexString(md.digest());\r
+                   stale = hkd.equals(dac_response);\r
+                   return false;\r
+               } else\r
+                   stale = true;\r
+           }\r
+           // basic things have been checked... now try the real thing\r
+           String a1, a2, ha1, ha2;\r
+           a1 = username + ":" + realm + ":" + passwd;\r
+           a2 = dac_method + ":" + dac_uri;\r
+           MessageDigest md = null;\r
+           try {\r
+               md = MessageDigest.getInstance(getAlgorithm());\r
+           } catch (NoSuchAlgorithmException algex) {\r
+               // fatal error, can't authenticate\r
+               return false;\r
+           }\r
+           md.update(a1.getBytes());\r
+           ha1 = StringUtils.toHexString(md.digest());\r
+           md.reset();\r
+           md.update(a2.getBytes());\r
+           ha2 = StringUtils.toHexString(md.digest());\r
+           md.reset();\r
+           String kd, hkd;\r
+           if (stale)   // KD( H(A1), unq(nonce-value) ":" H(A2)\r
+               kd = ha1 + ":" + old_nonce + ":" + ha2;\r
+           else\r
+               kd = ha1 + ":" + nonce + ":" + ha2;\r
+           md.update(kd.getBytes());\r
+           hkd = StringUtils.toHexString(md.digest());\r
+           if (!hkd.equals(dac_response))\r
+               return false;\r
+           // yeah!!!\r
+           return true;\r
+       }\r
+    }\r
+           \r
+    /**\r
+     * Attribute index - The list of allowed users.\r
+     */\r
+    protected static int ATTR_ALLOWED_USERS = -1 ;\r
+    /**\r
+     * Attribute index - The list of allowed groups.\r
+     */\r
+    protected static int ATTR_ALLOWED_GROUPS = -1 ;\r
+    /**\r
+     * Attribute index - The algorithm used\r
+     */\r
+    protected static int ATTR_ALGORITHM = -1 ;\r
+    /**\r
+     * Attribute index - The nonce time to live (in seconds)\r
+     */\r
+    protected static int ATTR_NONCE_TTL = -1 ;\r
+\r
+    static {\r
+       Attribute   a = null ;\r
+       Class       c = null ;\r
+       try {\r
+           c = Class.forName("org.w3c.jigsaw.auth.DigestAuthFilter");\r
+           //Added by Jeff Huang\r
+           //TODO: FIXIT\r
+       } catch (Exception ex) {\r
+           ex.printStackTrace() ;\r
+           System.exit(1) ;\r
+       }\r
+       // The list of allowed users\r
+       a = new StringArrayAttribute("users"\r
+                                    , null\r
+                                    , Attribute.EDITABLE) ;\r
+       ATTR_ALLOWED_USERS = AttributeRegistry.registerAttribute(c, a) ;\r
+       // The list of allowed groups:\r
+       a = new StringArrayAttribute("groups"\r
+                                    , null\r
+                                    , Attribute.EDITABLE);\r
+       ATTR_ALLOWED_GROUPS = AttributeRegistry.registerAttribute(c, a) ;\r
+       // The algorithm used for digest and checksum\r
+       a = new StringAttribute("algorithm"\r
+                               , null\r
+                               , Attribute.EDITABLE);\r
+       ATTR_ALGORITHM = AttributeRegistry.registerAttribute(c, a) ;\r
+       a = new IntegerAttribute("nonce_ttl"\r
+                                , new Integer(300)\r
+                                , Attribute.EDITABLE);\r
+       ATTR_NONCE_TTL = AttributeRegistry.registerAttribute(c, a) ;\r
+    }\r
+\r
+    /**\r
+     * The catalog of realms that make our scope.\r
+     */\r
+    protected RealmsCatalog catalog = null ;\r
+    /**\r
+     * Our associated realm.\r
+     */\r
+    protected ResourceReference rr_realm = null ;\r
+    /**\r
+     * The nam of the realm we cache in <code>realm</code>.\r
+     */\r
+    protected String loaded_realm = null ;\r
+\r
+    /**\r
+     * The challenge to issue to any client for Digest Authentication.\r
+     */\r
+    protected HttpChallenge challenge = null;\r
+\r
+    /**\r
+     * The nonce value of the digest, changed every X mn\r
+     */\r
+    protected String nonce = null;\r
+    /**\r
+     * The previous nonce value of the digest, changed every X mn\r
+     */\r
+    protected String old_nonce = null;\r
+\r
+    private long prev_date = 0;\r
+    private int nonce_ttl  = 600; /* 10mn by default */\r
+    /**\r
+     * Get a pointer to our realm, and initialize our ipmatcher.\r
+     */\r
+\r
+    protected synchronized void acquireRealm() {\r
+       // Get our catalog:\r
+       if ( catalog == null ) {\r
+           httpd server = (httpd) \r
+               ((FramedResource) getTargetResource()).getServer() ;\r
+           catalog = server.getRealmsCatalog() ;\r
+       }\r
+       // Check that our realm name is valid:\r
+       String name = getRealm() ;\r
+       if ( name == null )\r
+           return ;\r
+       if ((rr_realm != null) && name.equals(loaded_realm)) \r
+           return ;\r
+       // Load the realm and create the ipmtacher object\r
+       rr_realm = catalog.loadRealm(name) ;\r
+       loaded_realm = name;\r
+    }\r
+\r
+    /**\r
+     * Check that our realm does exist.\r
+     * Otherwise we are probably being initialized, and we don't authenticate\r
+     * yet.\r
+     * @return A boolean <strong>true</strong> if realm can be initialized.\r
+     */\r
+\r
+    protected synchronized boolean checkRealm() {\r
+       acquireRealm() ;\r
+       return true;// (ipmatcher != null) ;\r
+    }\r
+\r
+    /**\r
+     * Get the list of allowed users.\r
+     */\r
+\r
+    public String[] getAllowedUsers() {\r
+       return (String[]) getValue(ATTR_ALLOWED_USERS, null) ;\r
+    }\r
+\r
+    /**\r
+     * Get the list of allowed groups.\r
+     */\r
+\r
+    public String[] getAllowedGroups() {\r
+       return (String[]) getValue(ATTR_ALLOWED_GROUPS, null) ;\r
+    }\r
+\r
+    /**\r
+     * Get the algorithm used\r
+     */\r
+\r
+    public String getAlgorithm() {\r
+       return (String) getValue(ATTR_ALGORITHM, "MD5") ;\r
+    }\r
+\r
+    /**\r
+     * Lookup a user by its name.\r
+     * @param name The user's name.\r
+     * @return An AuthUser instance, or <strong>null</strong>.\r
+     */\r
+\r
+    public synchronized ResourceReference lookupUser (String name) {\r
+       if ( rr_realm == null )\r
+           acquireRealm() ;\r
+       try {\r
+           AuthRealm realm = (AuthRealm) rr_realm.lock();\r
+           return realm.loadUser(name) ;\r
+       } catch (InvalidResourceException ex) {\r
+           return null;\r
+       } finally {\r
+           rr_realm.unlock();\r
+       }\r
+    }\r
+\r
+    /*\r
+     * Is this user allowed in the realm ?\r
+     * First check in the list of allowed users (if any), than in the list\r
+     * of allowed groups (if any). If no allowed users or allowed groups\r
+     * are defined, than simply check for the existence of this user.\r
+     * @return A boolean <strong>true</strong> if access allowed.\r
+     */\r
+\r
+    protected boolean checkUser(AuthUser user) {\r
+       String allowed_users[] = getAllowedUsers() ;\r
+       // Check in the list of allowed users:\r
+       if ( allowed_users != null ) {\r
+           for (int i = 0 ; i < allowed_users.length ; i++) {\r
+               if (allowed_users[i].equals(user.getName()))\r
+                   return true ;\r
+           }\r
+       }\r
+       // Check in the list of allowed groups:\r
+       String allowed_groups[] = getAllowedGroups() ;\r
+       if ( allowed_groups != null ) {\r
+           String ugroups[] = user.getGroups() ;\r
+           if ( ugroups != null ) {\r
+               for (int i = 0 ; i < ugroups.length ; i++) {\r
+                   for (int j = 0 ; j < allowed_groups.length ; j++) {\r
+                       if ( allowed_groups[j].equals(ugroups[i]) ) \r
+                           return true ;\r
+                   }\r
+               }\r
+           }\r
+       }\r
+       // If no users or groups specified, return true\r
+       if ((allowed_users == null) && (allowed_groups == null)) \r
+           return true ;\r
+       return false ;\r
+    }\r
+\r
+    /**\r
+     * Catch set value on the realm, to maintain cached values.\r
+     */\r
+\r
+    public void setValue(int idx, Object value) {\r
+       super.setValue(idx, value);\r
+       if ( idx == ATTR_REALM ) {\r
+           // Initialize the filter challenge:\r
+           challenge = HttpFactory.makeChallenge("Digest");\r
+           challenge.setAuthParameter("realm", getRealm());\r
+       }\r
+       if ( idx == ATTR_NONCE_TTL ) {\r
+           if ( value instanceof Integer)\r
+               nonce_ttl = ((Integer) value).intValue();\r
+       }\r
+    }\r
+\r
+    /**\r
+     * Authenticate the given request.\r
+     * We first check for valid authentication information. If no \r
+     * authentication is provided, than we try to map the IP address to some\r
+     * of the ones we know about. If the IP address is not found, we challenge\r
+     * the client for a password.\r
+     * <p>If the IP address is found, than either our user entry requires an\r
+     * extra password step (in wich case we challenge it), or simple IP\r
+     * based authentication is enough, so we allow the request.\r
+     * @param request The request to be authentified.\r
+     * @exception org.w3c.tools.resources.ProtocolException if authentication\r
+     * failed\r
+     */\r
+\r
+    public void authenticate (Request request) \r
+       throws ProtocolException \r
+    {\r
+       // Are we being edited ?\r
+       if ( ! checkRealm() )\r
+           return ;\r
+       // Internal requests always allowed:\r
+       Client client = request.getClient() ;\r
+       if ( client == null )\r
+           return ;\r
+       // check for nonce validity\r
+       Date d = new Date();\r
+       if ((d.getTime() - prev_date) / 1000 > nonce_ttl) {\r
+           prev_date = d.getTime();\r
+           updateNonce();\r
+       }\r
+       DigestAuthContext dac = null;\r
+\r
+       // Check authentication according to auth method:\r
+       if ((request.hasAuthorization() && ! request.isProxy())\r
+           || (request.isProxy() && request.hasProxyAuthorization())) {\r
+           try {\r
+               dac = new DigestAuthContext(request);\r
+           } catch (DigestAuthFilterException ex) {\r
+               dac = null;\r
+           }\r
+           if (dac != null) {\r
+               ResourceReference rr_user = \r
+                   (ResourceReference)lookupUser(dac.dac_user) ;\r
+               try {\r
+                   AuthUser user = (AuthUser) rr_user.lock();\r
+                   // This user doesn't even exists !\r
+                   if ( user != null ) {\r
+                       // If it has a password check it\r
+                       if (user.definesAttribute("password") ) {\r
+                           if (dac.authenticate(user.getName(), \r
+                                                loaded_realm, \r
+                                                user.getPassword())) {\r
+                               request.setState(STATE_AUTHUSER, dac.dac_user);\r
+                               request.setState(STATE_AUTHTYPE, "Digest");\r
+                               request.setState(STATE_AUTHCONTEXT, dac);\r
+                               return;\r
+                           }\r
+                       }\r
+                   }\r
+               } catch (InvalidResourceException ex) {\r
+               } finally {\r
+                   rr_user.unlock();\r
+               }\r
+           }\r
+       }\r
+                       \r
+       // Every possible scheme has failed for this request, emit an error\r
+       Reply e = null;\r
+       HttpChallenge new_c;\r
+       if (dac != null && dac.stale) {\r
+           new_c = challenge.getClone();\r
+           if (new_c != null)\r
+               new_c.setAuthParameter("stale","true", false);\r
+           else\r
+               new_c = challenge;\r
+       } else\r
+           new_c = challenge;\r
+       if ( request.isProxy() ) {\r
+           e = request.makeReply(HTTP.PROXY_AUTH_REQUIRED);\r
+           e.setProxyAuthenticate(new_c);\r
+       } else {\r
+           e = request.makeReply(HTTP.UNAUTHORIZED);\r
+           e.setWWWAuthenticate (new_c);\r
+       }\r
+       HtmlGenerator g = new HtmlGenerator("Unauthorized");\r
+       g.append ("<h1>Unauthorized access</h1>"\r
+                 + "<p>You are denied access to this resource.");\r
+       e.setStream(g);\r
+       throw new HTTPException (e);\r
+    }\r
+\r
+    /**\r
+     * update the nonce string\r
+     */\r
+\r
+    private void updateNonce() {\r
+       updateNonce(getResource());\r
+    }\r
+\r
+    private synchronized void updateNonce(FramedResource fr) {\r
+       HTTPFrame htf;\r
+       if (fr instanceof HTTPFrame) {\r
+           htf = (HTTPFrame) fr;\r
+           try {\r
+               MessageDigest md = MessageDigest.getInstance(getAlgorithm());\r
+               md.update((new Date()).toString().getBytes());\r
+               try {\r
+                   md.update(htf.getETag().getTag().getBytes());\r
+               } catch (Exception ex) {\r
+                   // hum... try without it\r
+                   md.update(htf.getURLPath().getBytes());\r
+               }\r
+               byte b[] = md.digest();\r
+               if (nonce != null) \r
+                   old_nonce = nonce;\r
+               nonce = StringUtils.toHexString(b);\r
+               challenge.setAuthParameter("nonce", nonce);\r
+           } catch (NoSuchAlgorithmException algex) {\r
+               // bad algorithm, prevent access by firing an error\r
+/*             Reply error = request.makeReply(HTTP.INTERNAL_SERVER_ERROR) ;\r
+               error.setContent("The algorithm specified in the "+\r
+                                "DigestAuthFilterprocess filter "+\r
+                                "is not available, you are then unable to "+\r
+                                "access protected space");\r
+               throw new HTTPException(error);\r
+*/\r
+           }\r
+       }\r
+    }\r
+\r
+    /**\r
+     * Add the appropriate cache control directives on the way back.\r
+     * @param request The request that has been processed.\r
+     * @param reply The original reply.\r
+     * @return Always <strong>null</strong>.\r
+     */\r
+\r
+    public ReplyInterface outgoingFilter(RequestInterface request,\r
+                                        ReplyInterface reply) \r
+    {\r
+       Request req = (Request) request;\r
+       Reply rep = (Reply) reply;\r
+       if ( getPrivateCachability() ) {\r
+           rep.setMustRevalidate(true);\r
+       } else if ( getSharedCachability() ) {\r
+           rep.setProxyRevalidate(true);\r
+       } else if ( getPublicCachability() ) {\r
+           rep.setPublic(true);\r
+       }\r
+       if (req.hasState(AuthFilter.STATE_AUTHCONTEXT)) {\r
+           DigestAuthContext dac;\r
+           dac =(DigestAuthContext)req.getState(AuthFilter.STATE_AUTHCONTEXT);\r
+           if (dac.stale) {\r
+               rep.addAuthenticationInfo("nextnonce", nonce);\r
+           }\r
+       }\r
+       return null;\r
+    }\r
+\r
+    /**\r
+     * Initialize the filter.\r
+     */\r
+\r
+    public void initialize(Object values[]) {\r
+       super.initialize(values) ;\r
+       if ( getRealm() != null ) {\r
+           // Initialize the filter challenge:\r
+           challenge = HttpFactory.makeChallenge("Digest");\r
+           challenge.setAuthParameter("realm", getRealm());\r
+           updateNonce();\r
+           challenge.setAuthParameter("domain", getURLPath());\r
+           challenge.setAuthParameter("algorithm", getAlgorithm(), false);\r
+       }\r
+    }\r
+}\r